Лекция 13 (1160846)
Текст из файла
14
Лекция 13
Часть II. Объектно –ориентированные языки программирования
В основном мы занимаемся объектно-ориентированными языками программирования. Это наиболее активно используемые языки(кроме машинного).
(Java, JavaScript, Pithon, PHP, C#, C++)
О сути, вообще говоря, написано много всего. Какие требования предъявляются к объектно-ориентированным языкам?
1) инкапсуляция
2) наследование
3) динамический полиморфизм
(Заметим, что статический полиморфизм во всех пчти современных языках есть.)
Именно динамический полиморфизм делает язык объектно-ориентированным.
Есть языки объектно-ориентированные, а есть объектные. Остановимся на чисто объектных языках.
Наследование – отношение между классами.
Первая терминология:
Base – базовый класс
Derived – производный класс
Вторая терминология:
Superclass(суперкласс)
Subclass(подкласс)
Пункт 1. Наследование
Наследование – это отношение между базовыми и производными классами.
BaseDerived. Что это означает?
-
все объекты производнго класса принадлежат базовому классу(нарушена главная аксиома ОО ЯП)
-
объекты проиводного класса совестимы с объектами базовых классов по присваиванию и по передаче параметров(если формальный параметр принадлежит базовому классу, то фактический может являться объектом производного класса. Но не наборот!)
-
при наследовании могут
Отношение наследования распространено на ссылки и на указатели на объекты, то есть если есть Base * и Derived *, то они наследуют друг друга.
У ссылок и указателей появляется понятие динамического типа. (Статический тип определяется при объявлении. Собственно сами объекты данных обладают только статическим типом.) Для ссылок и указателей на классы вводится понятие динамического типа, так как объекты-ссылки и объекты-указатели базового класса могут ссылаться на объекты производных классов.
Динамический тип – это тип объекта, на который ссылка или указатель ссылаются в даный момент. Собственно объекты данных свой тип менять не могут.
Как синтаксически выражается наследование в различных языках программирования?
С++
Class derived: [ модификатор доступа ] Base{
Объявление новых членов
};
C++ - единственный язык, в котором доступ можно модифицировать. Если модератор доступа public, то унаследованные члены не меняют свои спецификаторы. Если private – все public и protected члены базового класса станут private членами производного класса. Если protected – те, что были public, станут protected. По умолчанию ставится модификатор private.
Пиведенный сснтаксис базовый, на него опираются остальные языки.
сlass D: B{
определение новых членов
};
Аналогично в Java - добавляется только ключевое слово extends.
Это была первая группа языков: C#, Java, Delphi.
Еще.
Язык Оберон и Оберон-2.
type D = RECORD(B);
объявление новых членов
END;
Синтаксис, заметим, восходит к языку Object Pascal:
type D = class(B)
объявление новых членов
end;
В Ада(версии 1995 и 2005 годов) появились тегированные типы – новое ключевое слово tag. Только такие типы могут наследоваться и наследовать.
type Base is tagged
record
………..
end record;
type Derived is tagged new //речь идет о появлении НОВОГО типа!
Base with record
Новые члены
end record;
Суть не меняется.
Нередка ситуация, когда новые члены-данные не добавляются.
Ада и Оберон отличаются от C++, C#, Java, Delphi(которые основаны на понятии класса) тем, что являются модульными языками программирования.
При определении типа новые члены – это члены данные. А операци по правилам Ады и Оберона определены в том же модуле, где находится модуль, от которого все наследуется. Распространена ситуация, когда мы добавляем только новые операции и ввобще не добавляем члены-данные. Как это записать на Аде?
В Оберон такая ситуация вполне допустима. А в Аде?
type Derived is tagged new
Base with record Base
end record;
А в Ада:
type DD is tagged new BB with null record;
При этом
Замечание1.
С точки зрения реализации новых членов все рассматриваемые языки позволяют линейное распределение памяти.: в этих языках обобщают понятие записи.
Наследование также позволяет линейное распределение памяти:
Но в языке SmallTalk распределение памяти нелинейно(ухудшает эффективность – доступ ко всем эементам при линейном распределении памяти выполнется одинаково, а внелинейном распределении он затруднен)
Во всех рассматриваемых языках программирования объекты данных имеют только фиксированный статический тип.
Base b; Derived d; (C, C++, C#)
(1)B=D;//допустимо
(2)D=B;//недопустимо
Пусть мы записали это на С++ Что произзошло? Тип объекта не менлся, ведь он вообще никогда не меняется(перераспределения памяти нам никто не проведет и не разрешит).
В (1) берется берется часть, принадлежащая базе, и присваивается. Обратное присваивание, очевидно, небезопасно., при присваивании мы должны быть уверены, что все члены-объекты производного класса лежат там, где надо иопределены.
Base b = new Base();
Derived d = new Derived();
Пункт 2. Инкапсуляция при наследоавнии классов как область действия.
Замечание. Мы рассматриваем 4 языка программирования классами и 2 модульных языка.
Остановимся на модификаторах доступа.
public
private
protected – наиболее нам интересен. Пусть есть класс В и 2 призводных от него класса D1 и D2 – защищенных, то есть с модификатором protected. У В есть защищенный член р. Как обращаться к нему из D1 и D2? Да так и обращаться
D1:
void f()[
p=0;//допустимо
this->p=0;//допустимо
}
Еще один пример.
D2 d2;
d2.p; //в C++, в Java нельзя: ссылка на идет через объект класса D2, она возмона только через объект класса D2 или через ссылку на себя самого.
Кроме этого: языках C#, Java существует модульная структура – понятия пространства имен, пакетного доступа.
Ада95 – package
Оберон - MODULE
Вывод: когда структура языка модульная, возникают проблемы с гранулированностью и инкапсуляцией.
В каком тексте должен определяться произвольный тип данных? Если есть один пакет, то все просто и проблем нет. А если пакетов несколько?
Как продемонстрировать приватность?
Base
Derived
T1 is private
T2 is private
Получается, что тип Base описан так:
type Base is tagged record ……все, что тут, является публичным……. end record
package P is
type Base is tagged private;
………………………………………..
Где-то в конце есть спецификация пакета:
private
type Base is tagged record
….............................................
end P;
Где будем наследовать Base?
Если в этом же пакете, то делаем это так:
type Derived is new Base with private
..............................................................
Если это определение в том же модуле, то структура этого Derived должна быть описана в приватной части пакета.
Но если оба типа описываются в одном модуле, и у типа Base есть приватные члены, даже тогда относительно derived у него нет ничего приватного.
Процедуры и функции, определенные в модуле, в том числе и функции derived, имеют полный доступ ко всему, и даже не смотря на то, что у подобного типа Base приватный доступ, то есть полной приватности нет.
Представим, что мы хотим унаседовать тип данных Base в другом модуле(например, в пользовательском – так часто делается).
package P1 is
type Derived is new Base with private;
procedure(D: Derived)
//Но так как модуль Р внешний относително Р1, то структура типа Base не видна. Таким образом, у нас либо нету приватных членов, либо все члены базового класса – приватные. Концепия protected и вовсе неосуществлена.
Для подобных целей придумали дочерний пакет.
package P.P1
Это означает, что фактически определяемое в нем как бы приписывается в конец приватной части в конце пакета. Наличие таких пакетов разрушает инкапсуляцию, разрешая доступ к частям отдельно написанного пакета. Это все равно, что написать свои переменные в namespace std;
Но с точки зрения внешней инкапсуляции Р1 точно так же инкпсулирован, как и Р.
Защищенных членов в Ада и Оберон нет, таким образом, в этих языках всего два вида доступа:
-
публичный
-
приватный – его по большому счету тоже нет, так как в Ада есть дочений пакет.
С точки зрения областей видимости(областей действия в некоторых языках)
Одно имя – несколько сущностей.
Пусть имя N объявлено(употреблено) 2 раза. Первое употребление – f1, второе – f2.
Возможно 3 случая.
-
Перегрузка(перекрытие) – overloading – случай, когда f1, f2 принадлежат одной области действия.(случай распространяется только на процедуры и функции)
-
f1 и f2 принадлежат разным областям видимости
-
области видимости не пересекаются – тогда это просто разные сущности с одним именем, дург другу не противоречащие
-
вложенные области видимости – возникает скрытие(hiding)
-
Пример.
class X{
int N;
};
class Y: public X{
double N;
.....};
Для того, чтобы ращличать две этих N, используются ключевые слова super, base, inherited:
super.N;
base.N;
inherited.N;
(Эти слова очень удобны, они позволяют держать в глове имя базового класса)
Но в общем случае скрытие – не очень хорошо
Пример.
class X{
public void g() {……….};
};
class Y: extend X{
private int g;
}
class Z extends Y{
………g….};
В С# и Java наследуются оба.
Если в классе Z написать:
g=0;//ошибка доступа
g();//тоже ошибка
Еще пример.
class X{
void g() {……….};
};
class Y: extend X{
int g;
}
Теперь внутри Z:
g=0;//
g();//
2 определяющих вхождения одного имени – нельзя. А вообще – g=0 – ошибка – мы его не видим, а g() – можно(видим)
В чисто модульной структуре разницы между доступом и видимостью нет.
-
Замещение(f1 и f2 – определеия виртуального метода)-overriding(переопределение, подмена)
Если f1 и f2 – один и тот же виртуальный метод, то f2 замещает f1. Требования для этого замещения:
-
f1 помечен как virtual
-
сигнатура методов полностью совпадает
Пример.
class Base{
void f();
virtual void g()l
};
class derived: public Base{
void g(int);//перегрузка
void g(); //замещение
void f(int); //просто скрытие – замещения нет, так как нет виртуальнсости
};
.Замечание. Тут не важно какие где модифкаторы доступа.
Замещение нужно нам, чтобы реализовать динамическое связывание.
Пункт 3. Динамический полиморфизм – это замещение.
Когда мы говоим о виртуальности, речь идет о виртуальности вызова.Виртуальный вызов всегда идет только через ссылку или указател(а не через объект – ведь сам объект, как мы помним, своего типа никогда не меняет)
Смотря на точку вызова, ничего сказать нельзя – мы ничего не знаем о типе ссылки.
В случае виртуального вызова:
1) определяется динамический тип
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.