20106-1 (Объектно-ориентированный подход к программированию), страница 2
Описание файла
Документ из архива "Объектно-ориентированный подход к программированию", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.
Онлайн просмотр документа "20106-1"
Текст 2 страницы из документа "20106-1"
Property MyList[Index: Integer]: Pointer read GetMyList write SetMyList; default; // Объявление индексированного свойства по умолчанию
End;
…
Var
MyClass: TmyClass;
Begin
MyClass:= TmyClass.Create;
…
MyClass [3]:=AnyObject; // Аналогично MyClass.MyList[3] ]:=AnyObject;
…
End;
Значение инкапсуляции в объектно-ориентированном программировании трудно переоценить. Чего стоит хотя бы то, что в Delphi к 100% полей классов доступ организован через свойства.
Наследование
Если Вы хотите изменить или дополнить поведение уже существующего класса, то нет никакой необходимости переписывать класс заново. Вам стоит воспользоваться наследованием. Вы должны объявить, что новый класс является потомком уже существующего и добавить в новый класс свойства и методы, которые Вам необходимы или перекрыть существующие методы и свойства:
Type
TmyFirstClass = class(TObject)
Private
Protected
Public
Constructor Create(Val: Integer); virtual;
end;
TmySecondClass = class(TMyFirstClass)
Private
AmyField: string; // Добавили новое поле
Protected
Procedure SetMyField(Val: string); // Добавили процедуру
Public
Constructor Create(Val: Integer); override; // Перегрузили конструктор
Property MyField: string read AmyField write SetMyField; // Добавили свойство
End;
Есть несколько правил области видимости объекта, которые помогут Вам разобраться со способами доступа к объектам и наследования объектов:
Поля, свойства и методы секции public не имеют ограничений на видимость.
Поля, свойства и методы секции private, доступны только в методах класса и в функциях, объявленных в том же модуле, где и класс.
Поля, свойства и методы секции protected тоже доступны только из методов класса и функций, объявленных в модуле, но они доступны в классах, являющихся потомками, в том числе и объявленных в других модулях.
При описании потомков, Вы можете изменять область видимости методов и свойств. Можно расширять область видимости, но не сужать. Т.е. если есть свойство в секции private, вы можете сделать его public, но не наоборот. Вот пример расширения области видимости:
Type
TmyClass=class(TObject)
Private
AmyField: Integer;
protected
property MyField: Integer read AmyField;
…
End;
TmySunClass = class(TMyClass)
…
Public
Property MyField; // Только упомянули его в другой секции и он поменял область видимости.
End;
Унаследованные поля и методы потомка имеются и в предке. При совпадении имен предка и потомка говорят о перекрытии потомком полей или методов предка. По способу вызова методы класса можно разделить на статические, виртуальные, динамические, перегружаемые и абстрактные. Абстрактные методы не имеют реализации и должны быть обязательно перекрыты в потомках. Абстрактными могут быть только виртуальные и динамические методы, например :
Type
TmyClass=class(TObject)
…
protected
procedure MyMethod(Val: Integer); virtual; abstract;
…
End;
Статические методы и поля в объектах-потомках перекрываются одинаково: Вы можете без ограничений перекрывать старые имена и при этом изменять тип методов, т.к. при перекрытии будет создано новое поле или метод с тем же именем. Перекрытый метод предка доступен в потомке через зарезервированное слово inherited.Type
TmyClass=class(TObject)
…
protected
procedure MyMethod;
…
End;
TmySunClass=class(TmyClass)
…
protected
procedure MyMethod(Val: Integer);
…
End;
…procedure TmySunClass.MyMethod(Val: Integer);begin
inherited MyMethod; // Метод предка без параметров, а метод потомка уже с параметром, т.е. мы поменяли тип процедуры.
…
end;
По умолчанию все методы - статические, поэтому их адреса известны уже на стадии компиляции, они будут вызываться при выполнении программы самым быстрым способом. Виртуальные и динамические методы описываются с помощью специальных директив virtual или dynamic. Эти методы могут быть перекрыты в потомке одноименными методами, имеющими тот же тип.
И наконец, хочу сказать, что Delphi не поддерживает множественного наследования. Для достижения того же самого, Вам придется использовать простое наследование от одного из нужных классов, а второй класс добавить как поле, и организовать доступ к свойствам второго класса через свойства наследника. Так можно соединить несколько классов. Другой способ - это создать класс-контейнер, в котором в качестве полей будут нужные включенные классы, а интерфейс доступа к ним организовать через свойства контейнера.
Полиморфизм
Указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. При обращении к свойствам и методам через этот указатель будет доступен именно экземпляр, адрес которого был присвоен, а не предок. Это и есть полиморфизм. Т.е. Вы можете иметь доступ к потомку через указатель объектного типа предка. Рассмотрим пример:
Type
TMyClass = class(TObject)
…
public
procedure GetData: string; virtual; abstract;
…
end;
TmySun1Class = class(TMyClass)
Protected
AmyField: string;
…
public
procedure GetData: string; override;
…
end;
TmySun2Class = class(TMyClass)
Protected
AmyField: Integer;
…
public
procedure GetData: string; override;
…
end;
…
implementation
procedure TmySun1Class.GetData: string;
begin
Result:= AmyField;
end;
procedure TmySun2Class.GetData: string;
begin
Result:=IntToStr(AmyField);
end;
…
Var
MyClass: TmyClass;
Class1: TmySun1Class;
Class2: TmySun2Class;
Begin
Class1:=TmySun1Class.Create;
Class2:=TmySun2Class.Create;
…
MyClass:= Class1;
Label1.Caption:= MyClass.GetData;
MyClass:= Class2;
Label2.Caption:= MyClass.GetData;
end;
Если посмотреть на этот код внимательно, то можно понять, что у компилятора нет возможности определить метод какого именно класса нужно вызывать. Поэтому, для определения адреса метода используются специальные таблицы, где хранятся адреса на виртуальные и динамические методы: VMT - таблица виртуальных методов и DMT - таблица динамических методов. Когда компилятор встречает указатель на виртуальный метод, то он ищет его адрес в VMT, где хранятся все адреса виртуальных методов класса унаследованных и перекрытых, поэтому такая таблица занимает много памяти, хотя и способ вызова метода работает сравнительно быстро. Динамические методы вызываются медленнее, но занимают меньше памяти, т.к. в них хранятся адреса динамических методов только данного класса и их индексы. При вызове динамических методов проводится поиск по этой таблице, если метод не найден, то поиск продолжается в DMT предков вплоть до Tobject, где вызывается стандартный обработчик вызова динамических методов. Зачем же нам все это надо? При проектировании иерархии классов предметной области, нужно статическими делать методы, которые не меняют своего поведения в потомках, т.е. при более детальном рассмотрении явления. Динамические и виртуальные методы могут меняться при переходе от общего к частному. Вспомните класс Tfield, который является общим предком для всех классов-полей таблицы. Потомки этого класса реализуют доступ к столбикам таблицы разных типов от целого числа до BLOB массива, однако, Вы можете иметь удобный доступ к этим потомкам через указатель типа Tfield и работать с ними одинаково.
Перегрузка методов, процедур и функций
Перегрузка объявляется с помощью зарезервированного слова overload. Рассмотрим пример:
Type
TmyDateClass=class(TObject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; // Объявляем возможность перегрузки
…
end;
TmySecondDateClass=class(TmyDateClass)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: string); overload; // Объявляем возможность перегрузки
…
End;…
implementationProcedure TmyDateClass .SetDate(Val: TDateTime);
Begin
Adate:=Val;
End;
Procedure TmySecondDateClass.SetDate(Val: string);
Begin
Adate:=StrToDate(Val);
End;
Во время работы программы, вы можете использовать во втором классе оба метода SetDate. Если Вы передадите в качестве параметра строку, то будет вызван метод второго класса, а если TdateTime, то метод предка. Можно перегружать и виртуальные методы, только вместо override нужно использовать reintroduce, например:
Type
TmyDateClass=class(TObject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; virtual; // Объявляем возможность перегрузки
…
end;
TmySecondDateClass=class(TmyDateClass)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: string); reintroduce; overload; // Объявляем возможность перегрузки
…
End;Вы можете использовать перегрузку и для процедур и функций необязательно при наследовании, и даже процедур и функций не классового типа, например:
Function Myfunction(Val: string): string; overload;
Begin
Result:=Val+' Ok!'
End;
Function Myfunction(Val: Extended): extended; overload;
Begin
Result:=Val/2;
End;
Или
TmyDateClass=class(Tobject)
private
Adate: TdateTime;
…
Public
Procedure SetDate(Val: TDateTime); overload; // Объявляем возможность перегрузки
Procedure SetDate(Val: string); overload; // Объявляем возможность перегрузки
…
End;
Параметры по умолчанию
Если Вам нужно создать функцию, которая в качестве параметров почти всегда получает одно и то же значение, но все-таки иногда оно может меняться, то Вам нужно объявить параметры по умолчанию в качестве формальных параметров, например:
Procedure MyProcedure(Val1: Extended; Val2: Integer = 2);
Begin
…
End;
Тогда Вы сможете вызывать ее такими способами:
MyProcedure(42.33); // аналогично MyProcedure(42.33, 2);
MyProcedure(15.6, 8);
Правило гласит, что все параметры по умолчанию должны быть сосредоточены в конце списка формальных параметров процедуры или функции. Опускать параметры по умолчанию, можно только начиная с конца списка, поэтому нужно упорядочивать параметры по умолчанию по степени важности.
Делегирование
Событие - это свойства процедурного типа, предназначенные для создания пользовательской реакции на внешние воздействия. События в Delphi реализуется за счет создания поля процедурного типа и объявления соответствующего свойства класса, например:
Type
TmyEvent = procedure(Sender: Tcomponent); of object; // определение процедурного типа
TmyClass=class(Tcomponent)
Private
FmyEvent: TmyEvent;
Protected
Procedure DoMyEvent;
published
property OnMyEvent: TmyEvent read FmyEvent write FmyEvent;
end;
Допустим, Вы определили функцию function MyProcedure(Sender: Tcomponent) для обработки события с помощью инспектора объектов или написали вручную и налету присвоили объекту: MyClass.OnMyEvent:=MyProcedure. При наступлении определенных условий Ваш класс может вызвать процедуру DoMyEvent, где будет вызвана Ваша процедура MyProcedure так:
Procedure TmyClass .DoMyEvent;
Begin
If Assigned(FmyEvent) then FmyEvent(Self);
End;Мы видим, что был проверен указатель на пользовательскую процедуру обработки события, и если он действителен, то вызывается пользовательская процедура - это и есть делегирование. Обратите внимание, что я разместил свойство OnMyEvent в секции published для того, чтобы программист мог воспользоваться инспектором объектов для написания процедуры обработки события.
Список литературы
Банников. Н.А. Объектно-ориентированный подход к программированию.
Для подготовки данной работы были использованы материалы с сайта www.stikriz.narod.ru.