ИНКАПСУЛЯЦИЯ
ООП требует чтобы данные были скрыты или инкапсулированы внутри использующего их класса. Использование методов доступамк внутренним данным объекта уменьшает шансы появления ошибок.(например класс TDate- методы могут проверить корректность изменения даты)
Инкапсуляция позволяет легко вносить изменения во внутреннюю структуру класса, не меняя его внешнее представление.
Таким образом, класс – это черный ящик, с небольшой видимой частью – интерфейсом класса. Интерфейс позволяет другим частям программы осуществлять доступ к объектам данного класса и использовать их. Это инкапсуляция, основанная на классах. Object Pascal позволяет использовать и другой подход – инкапсуляцию основанную на модулях.
Внешние по отношению к данному модулю программы могут использовать всё, что объявлено в интерфейсной части модуля, при этом они не имеют доступа к внутренней реализации используемых процедур и функций. Т.е. детали реализации, инкапсулированы внутри модуля, что также способствует разделению труда между программистами и позволяет легко вносить изменения в реализацию процедур и функций.
|
|
Для поддержки инкапсуляции основанной на классах в Object Pascal введены следующие спецификаторы доступа:
Public - определяет поля и методы к которым может обращаться любая часть программы.
Private - отмечает поля и методы класса, которые не доступны в не модуля, в котором определен класс.
Protected - определяет частично доступные поля и методы, доступ к ним имеют только методы данного класса и всех производных классов.
Как правило все поля класса относятся к категории PRIVATE а методы объявляются с использованием спецификатора PUBLIC.(это нормальный стиль ООП)
Спецификаторы доступа решают только одну задачу – с их помощью можно ограничить доступ к определенным членам класса, объявленного в интерфейсной части модуля, со стороны кода не являющегося частью этого модуля. Т.е. это значит что если в одном и том же модуле объявлены 2 класса, поля этих классов (даже если они объявлены как Private и Protected), ничем не защищены от взаимного доступа.
Type TDate=class
private
month, day, year:integer
Public
Procedure SelValue(m,d,y:integer); overload;
Procedure SetValue(newDate:TDateTime); overload;
Function,,,,,,,,,,,,,,
Function…………….
procedure increase.
…………….
End;
Procedure TDate.SetValue(m,d,y:integer);
Begin
Fdate:=EncodeDate(y,m,d);
End;
В данном примере метод GetText возвращает дату в виде строки. Добавление функиций типа Getmonth getyear getday не целесообразно, так как функции реализующие прямой доступ ко всем полям подряд могут уменьшить эффект от инкапсуляции, усложнив изменение внутренней структуры класса(при необходимости);
Изменим внутреннюю структуру класса. Вместо полей введем новое поле типа TdateTime.
|
|
При внесении данного изменения интерфейс класса никоим образом не изменится. -> не изменится и код прикладных программ, использующих данный класс.
Инкапсуляция при помощи свойств(новое в дельфи)
Свойство - это имя скрывающее реализацию доступа к информации класса. Благодаря использованию свойств можно существенно модифицировать внутреннее строение класса не внося при этом каких либо изменений в код, использующий этот класс. Синонимом слова свойство является виртуальное поле.
С точки зрения пользователя класса свойство этого класса выглядит как поле – его значение можно считывать и записывать. Например,
Edit1.Text:=Button1.Caption;
Очень напоминает чтение и запись полей. Однако на самом деле внутренняя реализация доступа к свойству может быть разной.
1) Можно напрямую отобразить свойство на внутренние поля класса.
2) Можно назначить методы, которые должны использоваться для осуществления, чтения, и записи значений свойства. Если свойства отражены на методы, то в рамках этих методов может выполняться целый комплекс действий, связанных с доступом к значению свойства.
Например:
При этом может осуществляться доступ к каким либо другим данным не являющимся частью класса, может выполняться разнообразные дополнительные действия(побочные эффекты). Например – перерисовка элементов управления после модификации одного из его свойств.
Технически свойства – это идентификатор, который отображается на данные или методы при помощи специальных ключевых слов read и write, например.
Введем в класс Tdate свойство Month/
Есть специальное ключевое свойство PROPERTY
Property month:integer read Fmonth
Write SetMonth
Для чтения используется частное поле Fmonth, а для изменения его значения метод SetMonth. Этот метод должен быть определен внутри класса. Влзможна любая другая комбинация.
Например, метод можно использовать и для чтения и для записи.
Или наоборот читать при помощи метода а записывать при помощи поля.
Property Month: integer read GetMonth write SetMonth;
Property Month:integer read GetMonth write FMonth;
Использование метода для модификации значений свойства является общепринятой практикой.
Обычно связанные со свойством данные и методы являются частными PRIVATE или защищенными PROTECTED, а само свойство является общедоступным.(обычная практика ООП). Т..е. любой доступ к этим данным и методам осуществляется только с использованием свойств.
àИнкапсуляция при помощи свойств является как упрощенной, так и расширенной.
+ Расширенной она является потому, что можно менять не только внутренне представление данных и функций доступа, но и добавлять новые функции доступа, не меняя при этом кода, который обращается к классу.
+ Упрощенной она является потому что в ситуациях, когда не требуется какого либо дополнительного кода, можно просто отобразить свойства на поля класса и не загромождать при этом внутреннее строение класса примитивными методами доступа к его внутренним полям.
Механизм завершения класса (ctrl shift C) может автоматически сформировать заготовки методов доступа к внутренним данным класса для любого из определенных свойств.
TMyClass
{ctrl shift C} /////// он добавит защищ.поле сам в определение класса
Property x:integer
End;
……………………
Implementation SetX;
…………………….
Procedure TMyclass
Begin
Fx:=Value;
End;
TMyclass
Private
FX:integer;
Procedure SetX(const value:integer);
Public
Property x:integer read FX write SetX;
End;
Примеры использования свойств:
1) класс TDate.
Type TDate = class
Private
fDate: TDateTime;
procedure SetDay(const Value:integer);
procedure SetMonth(const Value:ineteger);
procedure SetYear(const Value:integer);
function GetDay:integer;
function GetMonth: integer;
function GetYear:integer;
public
procedure SetValue(m,d,y: ineteger); overload;
procedure SetValue(NewDate:TdateTime); overload;
function heapYear:Boolean;
function GetTex: string;
procedure Increase;
//////добавляем свойства(всё проектируем на методы.)
property Day:integer read GetDay write SetDay;
property Month:integer read GetMonth write SetMonth;
|
|
property Year:integer read GetYear write SetYear;
end;
Пример реализации методов Set Get
Function TdateGetMonth;
Begin
Result:=MonthOf(FDate); ///в модуле DateUtils.
End;
Procedure TDateSetMonth(const value:integer);
Begin
fDate:=recodeMonth(fDate,Value);
End;
*В процессе работы приложение должно создать объект класса TDate (обычно на методе OnCreate формы). В приложении он может выглядит например: поля редактирования для дня, месяца и года, из них можно читать значение и записывать их в свойства, а можно читать из свойств и записывать в эти поля. + на форме надо расположить кнопки, соответствующие методам класса(Increase…..). Т.е. надо полностью протестировать класс.
Пример обращения к свойству:
EdDay.Text:=IntToStr(ADay.Day);///происх обр к методу Get. Get выбир тек день…и т.п.
Aday.Year:=StrToInt(EdYear.Text);
При объявлении свойства директиву write можно не использовать. И тогда свойство будет предназначено только для чтения (read only). Точно так же и с директивой read(будет write only).
Пример 2:
1 из задач инкапсуляции – сокращение числа глобальных переменных, используемых программой.
Если мы меняем представление поля класса, то изменения зактрагивают код нескольких методов этого класса.
Т.е. сокрытие информации позволяет инкапсулировать изменения.
Пример:
1) В программе существует несколько форм. Некоторые данные можно сделать доступными для всех форм, описав их как глобальные переменные в секции Interface модуля одной из форм.
Var Form1:TForm1;
NClicks:integer;
àДругие формы могут это использовать в своих целях. Данные связаны не с конкретным экземпляром формы, а со всей программой. Т.е. если есть 2 формы, они могут исп.эти данные совместно.
Если необходимо, чтобы каждая форма имела свой экземпляр данных, то необходимо добавить эти данные в класс формы.
Type TForm1=class(TForm)
Public
nClicks:integer;
end;
в этом случае при необходимости изменить внутреннюю структуру придется модифицировать код, использующий внутренние данные.
Таким образом данные можно объявить в Private и создать метод для чтения их значения.
Type TForm1=class(TForm)
private
nClicks:integer;
public
function GetClicks:integer;
end;
*Лучшим решением будет добавление свойства в форму.
|
|
Для этого
Type TForm1=class(TForm)
public
property NClick:integer;
end;
(и ctrl shift C)
Необходимо добавить обработку события OnClick для формы в рамках которого будет происходить увеличение значения свойства и отображение его значений в заголовке формы.
Procedure TForm1.FormClick(sender:object)
Begin
Inc(Nclicks);
Caption:=’Clicks’+IntToStr(Nclicks);
End;
Как создать еще одну формы такого же типа????
Procedure TForm1.btnCreateFormClick(sender:Tobject);
Begin
With TForm1.Create(Self) do
Show;
End;
3) Применение свойств для инкапсуляции доступа к компонентам формы.
Пример:
Имеется главная форма со строкой состояния в которой отображается информация, которая например может поступать из вспомогательных форм.
Main |
Form1.StatusBar1.SimpleText:=’!!!’;
Если в дальнейшем возникнет потребность поменять интерфейс …..
Property Statustext:string
Read GetTExt write SetText;
…………………
Function TForm1.GetText;string;
Begin
Result:=StatusBar1.SimpleText;
End;
Procedure TFrom1.SetTex(const Value:integer)
Begin
StatusBar1.SimpleText:=Value;
End;