Delphi 7. Занятие 2_7. Часть 4.
Задачи занятия:
- понятие инкапсуляции данных и методов в классе;
- зоны видимости из различных секций класса;
- секция класса PUBLISHED;
- секция класса PRIVATE;
- свойства класса (property);
- примеры доступа к данным и методам из разных секций;
- примеры доступа к данным через свойства.
Инкапсуляция данных и методов.
До сих пор мы создавали объекты, значения полей которых изменялись напрямую — через экземпляр класса.
Но одна из основных концепций построения класса — это непрямое взаимодействие с полями.
В механизме создания классов реализуется медицинский принцип — не навреди. Вообще строение класса напоминает айсберг — девять десятых его полей и методов скрыты «под водой» от пользователя.
Пользователь имеет доступ только к тем методам, которые определяют функциональность класса.
С полями ещё интереснее. Как правило, доступ к полям напрямую через экземпляр класса закрыт. Их значения изменяются или считываются через специальный механизм, называемый «свойство класса».
Принцип ограничения доступа к данным и методам класса называется инкапсуляцией.
Для достижения принципов инкапсуляции класс имеет внутреннею структуру — зоны (секции), в которых располагаются данные и методы с различной степенью доступности (об этом далее).
Зоны видимости.
В классе имеются следующие секции, регулирующие степень доступности находящихся там элементов.
- Секция «public» (общедоступный). К элементам этой секции можно обращаться из любого модуля. Доступ возможен из методов этого класса, его потомков и вообще из любых процедур.
- Секция «published» (публичный). К элементам этой секции можно обращаться из любого модуля. Особенность — элементы этой секции видны в инспекторе объектов (но для этого класс надо зарегистрировать в среде delphi как компонент; а это отдельная тема для разговора). Доступ возможен из методов этого класса, его потомков и вообще из любых процедур. Обычно эта секция идет первой в классе и её название опускается.
- Секция «protected» (защищённый). К элементам этой секции можно обратиться из модуля, в котором описан класс. В другом модуле доступ возможен из методов «дружественного» класса или его потомков.
- Секция «private» (частный). К элементам этой секции обратиться из других модулей нельзя. Доступ возможен из методов этого же класса, его потомков и процедур, расположенных в данном модуле.
- Секция «automated» (элементы так называемой автоматизации). К элементам этой секции можно обращаться из любого модуля. Элементы секции добавляются к интерфейсу OLE-объектов Автоматизации.
Замечание. Естественно, чтобы класс был доступен в другом модуле, его описание должно располагаться в секции interface. Секции могут повторяться в описании класса произвольное количество раз и в произвольном порядке.
Пример 1
Секция класса PUBLISHED
Создадим программу из 2-х форм. В первом модуле Unit1 в секции interface расположим описание класса.
Interface
…
TYPE
TmyClassA=class //если класс создаётся от Tobject, то Tobject можно не указывать;
//здесь мы явно не указали наименование секции класса; по умолчанию это секция published;
ii:integer;
function fA(i:integer):integer;
constructor create(k:integer=10);
end;
var
Form1: TForm1;
implementation
uses Unit2; //не забываем сделать ссылку на второй модуль, чтобы можно было открыть вторую форму;
{$R *.dfm}
//реализация конструктора
constructor TmyClassA.create(k:integer=10); //конструктор имеет параметр по умолчанию;
begin
ii:=k;
end;
function TMyClassA.fA(i:integer):integer;
begin
result:=i*2;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
unit2.Form2.Show; //отображаем вторую форму
end;
Во второй форме:
implementation
uses Unit1; //ссылка на первый модуль; иначе мы не увидим его секцию interface.
{$R *.dfm}
procedure pr;forward; //опережающее описание, так как сама процедура расположено по тексту ниже, чем её вызов.
procedure TForm2.Button1Click(Sender: TObject);
var xA:TMyClassA;xx,yy:integer;
begin
xA:=TMyClassA.Create; //используем вызов конструктора, используется его значение по умолчанию;
xx:=xA.ii;
yy:=xA.fA(11);
form2.Edit1.Text:=intToStr(xx);
form2.Edit2.Text:=intToStr(yy);
pr; //вызов процедуры
end;
procedure pr;
var xA:TMyClassA;xx,yy:integer;
begin
xA:=TMyClassA.Create(15); //передаём значение через конструктор
yy:=xA.fA(17);
xx:=xA.ii;
form2.Edit3.Text:=intToStr(xx);
form2.Edit4.Text:=intToStr(yy);
end;
В результате имеем:
- значение конструктора по умолчанию;
- результат работы функции fA;
- в конструктор передано значение «15»;
- вызов функции из обычной процедуры.
Видимость методов и полей, объявленных в секции public, аналогична (за исключение отображения в инспекторе объектов).
Секция класса PRIVATE
Дополним описание класса:
TYPE
TMyClassA=class
ii:integer;
function fA(i:integer):integer;
function fB(s:string=»):string;
constructor create(k:integer=10);
private //добавили секцию «private»
ss:string;
function fC(s:string):string;
end; //конец описания класса
TMyClassB=class(TMyClassA)
jj:integer;
end;
//метод fA
function TMyClassA.fA(i:integer):integer;
begin
result:=i*2;
end;
//метод fB
function TMyClassA.fB(s:string=»):string;
begin
result:=’Вызов метода fC из метода fB. ‘+#13#10+ fC(‘private’); //вызов private метода fC из published метода fB.
end;
//метод fC
function TmyClassA.fC(s:string):string; //private метод
begin
ss:=’функция fC расположена в секции: ‘+s; //поле ss описано в private секции класса
result:=ss;
end;
Обращаемся к классу из Unit1.
procedure prC(s:string);
var ss:string; sAA:TMyClassA;
begin
sAA:=TMyClassA.create();
ss:=sAA.fC(‘private секция. Вызов из обычной процедуры.’); //вызов метода fC, расположеного в private секции. Метод доступен из процедуры.
form1.memo1.Clear;
form1.Memo1.Text:=ss;
xB:=TMyClassB.create;
form1.Memo2.Clear;
form1.Memo2.Text:=xB.fC(‘private»‘+#13#10+’метод вызван через экземпляр класса-потомка TMyClassB’);
end;
procedure TForm1.Button2Click(Sender: TObject);
var s:string; sA:TMyClassA;
begin
sA:=TMyClassA.create();
s:=sA.fC(‘private.’); //вызов private метода fC. //метод доступен из обработчика события
form1.memo1.Clear;
form1.memo1.Text:=s;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
prC(‘private’); //вызываем процедуру
//
end;
procedure TForm1.Button4Click(Sender: TObject);
var s:string; sA:TMyClassA;
begin
sA:=TMyClassA.create();
s:=sA.fB; //метод fB доступен из обработчика события
form1.memo1.Clear;
form1.memo1.Text:=s;
//
end;
Метод fB объявлен в секции published. Он доступен отовсюду и в модуле Unit1, и в модуле Unit2.
Доступ возможен из обработчиков событий компонент формы, из методов класса, из методов класса-потомка (класс TMyClassB ) и из обычных процедур.
Метод fC объявлен в секции private. Он доступен отовсюду в модуле Unit1, но в модуле Unit2 доступа к нему нет (на рисунке процедура pr расположена в модуле Unit2):
Использование секции PROTECTED класса
Методы этой секции доступны из модуля Unit1, но не видны в модуле Unit2.
Однако есть такое понятие, как дружественный класс. Если в Unit2 объявить:
TmyClassZ=class(TmyClassA);
то секция protected становится видна для экземпляров этого класса!
Сведём полученные сведения в таблицу:
Для Unit1:
В нашем случае дружественный класс — TmyClassZ=class(TmyClassA);
Как правило, описания классов размещают в отдельном модуле, что обеспечивает недоступность экземпляров объектов к секциям private и protected.
Лазейка остаётся только для дружественных классов для методов, расположенных в секции protected.
Свойства класса.
До сих пор мы обращались к полям класса напрямую. Хотя в принципе это допустимо, но никто так не делает.
В классах обращение к значению полей осуществляется через специальный механизм — свойства класса.
Вот пример такого обращения
в Unit1
Unit1
TYPE
TMyClassX=class
private
poleInt:integer;
procedure setProp(Value:integer);
function GetProp:integer;
public
property IntegerValueA:Integer read poleInt write poleInt;
property IntegerValueB:Integer read poleInt write setProp;
property IntegerValueC:Integer read GetProp write setProp;
end;
function TMyClassX.getProp:integer;
begin
if poleInt<=0 then
showmessage(‘Внимание! значение не положительное’); //showmessage — модальное окно сообщения.
result:=poleInt;
end;
procedure TMyClassX.setProp(Value:integer);
begin
if not (Value=0) then poleInt:=Value
else
showmessage(‘Нулевое значение недопустимо.Введите ещё раз’);
end;
в Unit2
procedure TForm2.Button1Click(Sender: TObject);
var vX:TMyClassX; i:integer;
begin
vX:=TMyClassX.Create;
vX.IntegerValueA:=form2.SpinEdit1.Value;
i:=vX.IntegerValueA;
form2.Edit1.Text:=intToStr(i);
end;
procedure TForm2.Button2Click(Sender: TObject);
//проверяем, не вводим ли мы нулевое значение (отрицательное значение допускается)
var vX:TMyClassX; i:integer;
begin
vX:=TMyClassX.Create;
vX.IntegerValueB:=form2.SpinEdit1.Value; //записываем значение поля через свойство
i:=vX.IntegerValueB;//читаем поле напрямую
form2.Edit1.Text:=intToStr(i);
end;
procedure TForm2.Button3Click(Sender: TObject);
//проверяем, не вводим ли мы нулевое значение
var vX:TMyClassX; i:integer;
begin
vX:=TMyClassX.Create;
vX.IntegerValueC:=form2.SpinEdit1.Value; //записываем значение поля через свойство
i:=vX.IntegerValueC;//читаем поле через свойство.
//Проверка чтобы поле было больше нуля.
form2.Edit1.Text:=intToStr(i);
end;
Рассмотрим класс TmyClassX
В нём объявлены две секции. Первая секция private. Поля и методы этой секции недоступны в Unit2.
Вторая секция — public. Расположенные в неё методы и поля доступны изо всех модулей.
В секции private объявлено поле poleInt:integer; и два метода:
procedure setProp(Value:integer);
function GetProp:integer;
Эти методы обслуживают конструкцию, называемую «свойство».
Свойство выглядит следующим образом:
property имя_свойства:тип read имя_поля разрешение write имя_поля;
или:
property имя_свойства:тип read имя_метода_для_чтения write имя_метода_для_записи;
Возможны также вариации.
Приведём пример:
public
property IntegerValueA:Integer read poleInt write poleInt;
property IntegerValueB:Integer read poleInt write setProp;
property IntegerValueC:Integer read GetProp write setProp;
setProp — всегда процедура строго типа procedure имя_процедуры(Value:тип);
GetProp —всегда функция строго вида function имя_функции:тип;
Если отсутствует ключевое слово read, то свойство нельзя прочитать. Если отсутствует ключевое слово write, то свойство нельзя записать.
Примеры функции и процедуры даны выше.
Свойство работает следующим образом.
Если мы хотим прочитать значение поля:
vX:=TMyClassX.Create;
i:=vX.IntegerValueA;//читаем поле напрямую
Если хотим записать значение поля.
vX.IntegerValueA:=form2.SpinEdit1.Value; //записываем значение поля напрямую.
i:=vX.IntegerValueС;//читаем поле через функцию GetProp, которая вызывается неявно.
vX.IntegerValueС:=form2.SpinEdit1.Value; //записываем значение поля через процедуру setProp, которая также вызывается неявно.