Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
8-14.docx
Скачиваний:
6
Добавлен:
01.03.2025
Размер:
52.71 Кб
Скачать

9. Инкапсуляция

Как правило, объект — это сложная конструкция, свойства и поведение составных частей которой находятся во взаимодействии. К примеру, если мы моделируем взлетающий самолет, то после набора им определенной скорости и отрыва от земли принципы управления им полностью изменяются. Поэтому в объекте "самолет" явно недостаточно просто увеличить значение поля "скорость" на несколько километров в час — такое изменение должно автоматически повлечь за собой целый ряд других.

При создании программных объектов подобные ситуации можно моделировать, связывая со свойствами необходимые методы. Понятие инкапсуляции соответствует этому механизму.

Классическое правило объектно-ориентированного программирования утверждает, что для обеспечения надежности нежелателен прямой доступ к полям объекта: чтение и обновление их содержимого должно производиться посредством вызова соответствующих методов. Это правило и называется инкапсуляцией. В старых реализациях ООП (например, в Turbo Pascal) эта мысль внедрялась только посредством призывов и примеров в документации; в языке же Object Pascal есть соответствующая конструкция. В Delphi пользователь вашего объекта может быть полностью отгорожен от полей при помощи свойств

Инкапсуляция — это свойство, благодаря которому разработчику, использую­щему определенный строительный блок (код), не нужно знать, как он на самом де­ле реализован и работает для корректного использования этого строительного бло­ка. Что это значит? Когда вы садитесь за руль автомобиля, вы знаете, как устроен его двигатель или коробка передач? Лично я понятия не имею. Нет, я в курсе, что двигатель ворочает коробку передач, а та передает свои усилия на колеса, и этого мне вполне достаточно, чтобы повернуть ключ, воткнуть первую передачу и начать движение.

 

10. Полиморфизм — это возможность использовать одинаковые имена для методов, входящих в различные классы.  Концепция полиморфизма обеспечивает при применении метода к объекту использование именно того метода, который соответствует классу объекта. Полиморфизм означает, что один и тот же метод выполняется по-разному для различных объектов. Например, метод класса Музыкальный инструмент - PlayMusicForAnOrchestra (играй музыку для оркестра) - может быть определен как общий метод, который может использоваться с любой категорией музыкальных инструментов. Этот метод написан таким образом, что не важно, какой именно инструмент получает задание играть, однако для классов, описывающих конкретные инструменты, данный метод должен быть переопределен (override), что даст возможность определить конкретные действия, учитывающие особенности данного инструмента.

Полиморфизм – это свойство классов решать схожие по смыслу задачи разными способами. Поведение класса определяется набором входящих в его состав методов. Изменяя алгоритм того или иного метода в потомках класса можно придавать специфические действия классу-потомку, которые отсутствуют у класса-родителя.

11.такой же что и 9!!!!

12. Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее важная черта классов — способность наследовать поля, методы и свойства других классов. Чтобы пояснить сущность наследования, обратимся к примеру с измерителями ресурсов.

Есть несколько правил области видимости объекта, которые помогут Вам разобраться со способами доступа к объектам и наследования объектов:

  1. Поля, свойства и методы секции public не имеют ограничений на видимость.

  1. Поля, свойства и методы секции private, доступны только в методах класса и в функциях, объявленных в том же модуле, где и класс.

  1. Поля, свойства и методы секции protected тоже доступны только из методов класса и функций, объявленных в модуле, но они доступны в классах, являющихся потомками, в том числе и объявленных в других модулях.

При описании потомков, Вы можете изменять область видимости методов и свойств. Можно расширять область видимости, но не сужать. Т.е. если есть свойство в секции private, вы можете сделать его public, но не наоборот. Вот пример расширения области видимости:

 Класс TDiskGauge описывает измеритель дискового ресурса и непригоден для измерения ресурса другого типа, например оперативной памяти. С появлением измерителя оперативной памяти нужен новый класс объектов:

Code:

type

TMemoryGauge = class

   FPercentCritical: Integer;

   constructor Create;

   function GetPercentFree: Integer;

   procedure SetPercentCritical (Value: Integer) ;

   procedure CheckStatus;

   property PercentFree: Integer read GetPercentFree;

   property PercentCritical: Integer

       read FPercentCritical write SetPercentCritical;

end;

 

 

Поля, методы и свойства класса TMemoryGauge аналогичны тем, что определены в классе TDiskGauge. Отличие состоит в отсутствии поля DriveLetter и другой реализации конструктора Create и метода GetPercentFree. Если в будущем появится класс, описывающий измеритель ресурса какого-то нового типа, то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования атрибутов при определении новых классов, воспользуемся механизмом наследования. Прежде всего выделим атрибуты, общие для всех измерителей ресурсов, в отдельный класс TResourceGauge:

Code:

type

TResourceGauge = class

  FPercentCritical: Integer;

 constructor Create;

 function GetPercentFree: Integer;

 procedure SetPercentCritical (Value: Integer) ;

 procedure CheckStatus;

 property PercentFree : Integer read GetPercentFree;

 property PercentCritical: Integer

      read FPercentCritical write SetPercentCritical;

end;

 

constructor TResourceGauge.Create;

begin

FPercentCritical := 10;

end;

 

function TResourceGauge.GetPercentFree: Integer;

begin

Result := 0;

end;

 

procedure TResourceGauge.SetPercentCritical (Value: Integer);

begin

if (Value >= 0) and (Value < 100) then FPercentCritical := Value;

end;

 

procedure TResourceGauge.CheckStatus;

begin

if GetPercentFree <= FPercentCritical then Beep;

end;

 

 

При реализации класса TResourceGauge ничего не известно о том, что в действительности представляет собой ресурс, поэтому функция GetPercentFree возвращает нуль. Очевидно, что создавать объекты класса TResourceGauge не имеет смысла. Для чего тогда нужен класс TResourceGauge? Ответ: чтобы на его основе породить два других класса — TDiskGauge и TMemoryGauge, описывающих конкретные виды измерителей ресурсов, — измеритель диска и измеритель памяти:

Code:

type

TDiskGauge = class(TResourceGauge)

  DriveLetter: Char;

 constructor Create (ADriveLetter: Char) ;

 function GetPercentFree: Integer;

end;

 

TMemoryGauge = class(TResourceGauge)

 function GetPercentFree: Integer;

end;

 

 

Классы TDiskGauge и TMemoryGauge определены как наследники TResourceGauge (об этом говорит имя в скобках после слова class). Они автоматически включают в себя все описания, сделанные в классе TResourceGauge и добавляют к ним некоторые новые. В результате формируется следующее дерево классов (рис. 1):

 

Рисунок 1

 

Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком. Естественно, что класс, от которого происходит наследование, выступает в роли базового, или предка. В примере класс TDiskGauge является непосредственным потомком класса TResourceGauge. Если от TDiskGauge породить новый класс, то он тоже будет потомком TResourceGauge, но уже не таким близким, как TDiskGauge.

Очень важно, что в отношениях наследования любой класс может иметь только одного непосредственного предка и сколь угодно много потомков. Поэтому все связанные отношением наследования классы образуют иерархию. Примером иерархии классов является библиотека Visual Component Library (VCL); с ее помощью в Delphi обеспечивается разработка Windows-приложений.

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class). Классы, которые произошли от базового, называются потомкаминаследниками или производными классами (англ. derived class). В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML. Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя. Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе. Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

13. Унаследованные поля и методы потомка имеются и в предке. При совпадении имен предка и потомка говорят о перекрытии потомком полей или методов предка. По способу вызова методы класса можно разделить на статические, виртуальные, динамические, перегружаемые и абстрактные. Абстрактные методы не имеют реализации и должны быть обязательно перекрыты в потомках. Абстрактными могут быть только виртуальные и динамические методы, например :

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. Эти методы могут быть перекрыты в потомке одноименными методами, имеющими тот же тип.

14. и 15. Конструкторы и деструкторы отвечают за существование объекта в памяти, т.е. выделяют память для экземпляра класса, затем и освобождают ее.

Конструктор – это специальный вид подпрограммы, присоединенный к классу. Его на-значение – создавать представителей (экземпляры) класса. Он ведет себя как функция, которая возвращает ссылку на вновь созданный экземпляр класса, т.е. на объект. Одновременно выделяется память для хранения значений полей экземпляра класса.

Деструктор – это специальная разновидность подпрограммы, присоединенной к классу. Его назначение заключается в уничтожении экземпляра класса, т.е. объекта и освобождении памяти, выделенной под экземпляр.

Синтаксис объявления конструкторов и деструкторов:

Type <имя класса>=Сlass[{Имя родительского класса>)] . . . Constructor Имя конструктора>[(<параметры>)]; [Override;] Destructor <имя деструктора>[(<параметры>)}; [Override;] End;

Примечания: • Объявляются конструкторы и деструкторы, как правило, в разделе Public класса. • В классе может быть объявлено несколько конструкторов, однако чаще бывает один конструктор. Общепринятое имя для единственного конструктора Create. • В одном классе может быть объявлено несколько деструкторов, но чаще бывает один деструктор без параметров (всегда!) с именем Destroy. • За объявлением деструктора по имени Destroy следует указывать ключевое слово-директиву Override, разрешающее выполнение предусмотренных по умолчанию действий для уничтожения экземпляра объекта, если при его создании возникла какая-либо ошибка. Фактически Override переопределяет метод предка • Метод Free так же удаляет (разрушает) экземпляры класса, предварительно проверяя их на Nil.

Реализация конструкторов

В задачу конструктора входит создание экземпляра класса и выполнение операторов, содержащихся в его теле. Назначение кода внутри конструктора – инициализировать только что созданный экземпляр объекта. Синтаксис реализации конструктора:

Constructor <имя класса>.<имя конструктора>[(<параметры>}}; [<блок объявлений>} Begin <Исполняемые операторы> End;

Реализация наследуемых конструкторов.

Constructor <имя класса>.<имя конструктора>[(<параметры>)]; [<блок обьявлений>} Begin Inherited <имя конструктора>[(<параметры>)}; <инициализация собственных полей> End;

Примечания: • Следует убедиться, что для каждого поля в конструкторе предусмотрен оператор присвоения, и что все поля переходят из неопределенного состояния в какое-то конкретное, предусмотренное по умолчанию, хотя и известно, что транслятор сам выполнит инициализацию нулевыми значениями ('', Nil, False - для других полей). Для неклассовых полей такая инициализация вполне допустима, поскольку позволяет их использовать в дальнейшем. • Для вызова наследуемого конструктора следует использовать ключевое слово Inherited, которое фактически обеспечивает доступность перекрытого метода. Сила оператора в том, что он вызывает старое, а затем возвращается к новому. • Как правило, следует вызывать подходящий наследуемый конструктор в первом исполняемом операторе. • Только, если у пользовательского класса нет новых полей, можно не создавать для него конструктор. • Хотя в объявлениях конструкторов и не указывается тип возвращаемого результата, и они выглядят, как объявления процедур, конструкторы используются скорее как функции, а не как процедуры. Можно сказать, что конструктор является неявной функцией - он возвращает нового представителя того класса, который использовался при его вызове. Нет необходимости в явном виде задавать тип возвращаемого результата, поскольку этот тип и так известен на момент вызова - это тип использованного в вызове конструктора класса. • Внутри конструктора отсутствует и явное присвоение возвращаемого значения. Такое возможно, поскольку он всегда возвращает ссылку (адрес) на вновь созданный и инициализированный экземпляр класса. • Начиная с класса TComponent конструктор Create стал виртуальным и при его переопределении необходимо указывать слово-директиву Override.

Реализация деструкторов

Деструктор уничтожает экземпляр класса, который был использован при его вызове, автоматически освобождая любую динамическую память, которая ранее была зарезервирована конструктором, закрывает файлы и т.п. операции. Программист ответственен за вызов деструкторов для всех экземпляров класса, если были зарезервированы подчиненные объекты.

Синтаксис реализации деструктора:

Destructor <имя класса>.<имя деструктора>[(<параметры>)]; [<блок объявлений>] Begin <исполняемые операторы> End;

Реализация наследуемых деструкторов Если использовать механизм наследования деструкторов, то можно упростить задачу уничтожения экземпляров класса, таким образом, чтобы каждый раз заботиться лишь об уничтожении тех полей, которые были добавлены в данном классе. Всю работу по очистке наследуемых полей можно возложить на наследуемые деструкторы. Для вызова наследуемого деструктора необходимо используется ключевое слово Inherited.

Синтаксис объявления наследуемого деструктора следующий:

Destructor <имя класса>.<имя деструктора>[(<параметры>)}; [<блок объявлений>] Begin <уничтожение собственных полей> Inherited <имя деструктора>[{<параметры>)]; End;

Примечания: • Внутри деструктора есть доступ не только к обычным идентификаторам, но и к полям экземпляра класса, инкапсулированным при его определении. • Исполняемые операторы деструктора должны позаботиться обо всех операциях очистки, необходимых для уничтожения экземпляра класса. Код деструктора должен уничтожить все внутренние экземпляры объектов и освободить динамическую память, которая была зарезервирована во время существования экземпляра класса. Однако нет необходимости явно устанавливать в нулевые значения поля прямого доступа. • Необходимость в объявлении деструкторов с параметрами возникает очень редко. Обычно все, что нужно сделать деструктору – уничтожить экземпляр класса, и вся необходимая для этого информация и так доступна ему. • Если у класса нет полей косвенного доступа, то деструктор можно не создавать для такого пользовательского класса.

Вызов конструкторов

Для того чтобы вызвать конструктор, необходимо правильно объявить и определить класс. Объявление класса должно быть доступно, т.е. он должен находится в области видимости из того места, где конструктор будет вызываться.

Если в пользовательском классе не определен конструктор, то по умолчанию будет использоваться конструктор, унаследованный от класса-потомка. В любом случае у всех объектов есть доступ к конструктору Create, определенному в классе TObject.

Определение класса создает активную структуру, способную создавать представителей этого класса. Объекты, которые создаются с помощью определения класса, способны хранить ссылки на вновь создаваемые объекты.

Var <имя обьекта>: <имя класса>; // Объявление переменной -указателя Begin <имя обьекта>:=<имя класса>.<имя конструктора>[(<параметры>)];

• Если вызвать конструктор от имени объекта, то новый объект не будет создан (память не выделяется), но будут выполнены операторы, указанные в коде конструктора. • Конструктор может также вызываться с помощью переменной типа указателя на класс.

Вызов деструкторов

Деструкторы вызываются точно так же, как и большинство других методов класса – через его действующий экземпляр.

Синтаксис вызова конструктора следующий:

<имя объекта>.<имя деструктора>[(<параметры>)];

Примечания: • После вызова деструктора объект становится недоступен. Целесообразно присваивать объекту значение Nil сразу после его уничтожения, чтобы в дальнейшем можно было бы проверить его существование. • Не следует вызывать деструкторы непосредственно. Вызов метода Free, наследуемого от TObject.Free, сравнивает указатель экземпляра со значением Nil перед тем, как вызвать деструктор Destroy.

Вызовы конструкторов и деструкторов визуальных компонентов Delphi. Любой компонент, попавший в ваше приложение при визуальном проектировании, включается в определенную иерархию объектов, которая замыкается на форме (класс TFonn). Поэтому вызов конструкторов и деструкторов всех компонентов формы производится автоматически при инициализации и удалении формы, незримо для программиста.

Сами формы создаются и уничтожаются приложением – глобальным объектом с именем Application. В файле-проекте с расширением *.Dpr можно увидеть вызов конструктора формы в виде строки:

Application.CreateForm(TForml, Fonnl);

Динамическое создание объектов. Пользователь может создать объект и программным путем:

Var Mem: TMemo; Begin Mem:=TMemo.Create(Self); // Создание экземпляра класса TMemo Mem.Parent:=Self; // Form1 указывать не обязательно Mem.Name:=’TmpMem’; // Присвоение имени компоненту FindComponent(‘TmpMem’).Free; // Удаление компонента

Любой создаваемый компонент должен перекрывать конструктор и, возможно, деструктор своего родительского класса, если в компоненте создаются вспомогательные объекты. Если, например, в компоненте используется внутренний таймер (для отсчета времени или для анимационных эффектов), его следует создать в конструкторе:

type

TMyComponent = class (...)

private г.

FTimer: TTimerepublic

constructor Create(AOwner: TComponent); override; end;

constructor TMyComponent.Create(AOwner: TComponent); begin

inherited; // Обязательно вызываем конструктор родителя! FTimer := TTimer.Create(Self); // Создаем внутренний таймер end;

Если внутренний объект не является компонентом (наследником TComponent) и, следовательно, не будет автоматически удаляться при разрушении компонента, следует перекрыть также деструктор:

type

TMyComponent « class (...) private

FStrings: TStringList; public

constructor Create(AOwner: TComponent); override; destructor Destroy; override; end;

constructor TMyComponent.Create(AOwner: TComponent); begin

inherited; // Обязательно вызываем конструктор родителя! FStrings := TStringList.Create; // Создаем внутренний объект end;

destructor TMyComponent.Destroy;

begin

FStrings.Free; // Разрушаем внутренний объект inherited // Вызываем унаследованный деструктор

end;

Конструкторы также перекрываются для установки умалчиваемых значений новых свойств.

При перекрытии конструктора (деструктора) следуйте двум важным правилам:

• объявляйте метод с директивой override;

• обязательно вызывайте в новом методе унаследованный конструктор (деструктор).

Только при выполнении этих правил будут нужным образом инициализированы и удалены все унаследованные поля.

Порядок вызова унаследованного конструктора зависит от того, должны ли быть инициализированы унаследованные поля к моменту выполнения метода: если должны, вызывайте унаследованный конструктор до обращения к этим полям. Как правило, вызов унаследованного конструктора осуществляется в самом первом операторе нового, а вызов унаследованного деструктора — в самом последнем операторе.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]