Объект используется внешней программой, которая обеспечивает весь интерфейс пользователя: кнопки, индикаторы и пр. И здесь возникает изрядная проблема: методы объекта при своем выполнении должны взаимодействовать с элементами интерфейса. Скажем, при смене частоты вращения на экране должен «ползать» полосковый индикатор. Но ведь объект пишется один раз и должен быть пригоден для использования в разных программах, с разным интерфейсом. Поэтому, как правило, внутри объекта никаких ссылок на формы, кнопки, поля ввода… нет .
Как же быть? Выход, разумеется, есть. Можно передать в объект ссылку на любую процедуру, описанную во внешней программе, и внутри методов объекта эту процедуру вызывать. Передача ссылки на процедуру (точнее, на метод другого объекта – такая процедура может быть, например, методом главной формы программы) требует использования особого типа данных, который описывается как
TYPE TP=procedure of object;
В переменной типа TP может храниться адрес метода любого другого объекта. В объекте TMotor создадим поле p типа TP. При создании объекта нужно гарантировать, что в поле p будет занесен адрес процедуры из внешней программы. Для этого достаточно ввести параметр у метода-конструктора Create:
CONSTRUCTOR TMotor.Create(pp:TP);
BEGIN
INHERITED Create;
p:=pp; { запоминаем ссылку на процедуру в поле p }
RPM:=0; { начальная частота }
Power:=0; { начальная потребляемая мощность }
br:=false; { аварии пока нет }
Direction:=CLOCKWISE { по часовой }
END;
Хочешь создать наш объект – изволь подать ему на вход процедуру.
Теперь внутри методов в любом месте можно вызвать внешнюю процедуру, просто написав p (как это было сделано в методе Feed).
Теперь соберем все сказанное вместе, да еще добавим метод Start, разгоняющий мотор до номинальной частоты, Stop (плавное торможение) и Abort (авария):
unit motor;
INTERFACE
TYPE Tdir=(CLOCKWISE, COUNTERCLOCKWISE);
TP=procedure of object; { см. следующий раздел методички!}
TMotor=CLASS
private
{ поля }
RPM:WORD; { частота вращения }
Power:REAL; { мощность }
Direction:TDir; { направление вращения }
p:TP;
br:boolean; { авария }
PROCEDURE Feed(newRPM:WORD); { смена частоты }
FUNCTION GetRPM:word;
FUNCTION GetPower:REAL;
FUNCTION GetNominal: WORD;
FUNCTION GetDr: TDir;
PROCEDURE Flip(newd:TDir);
public
{ СВОЙСТВА }
property n:word READ GetRPM WRITE Feed;
property pwr:real read GetPower;
property Nominal:WORD read GetNominal;
property dir:TDir read GetDr write Flip;
{ МЕТОДЫ }
CONSTRUCTOR Create(PP:TP);
DESTRUCTOR Free;
PROCEDURE Start; { запуск }
PROCEDURE Stop; { останов }
PROCEDURE Abort; { аварийный стоп }
END;
implementation
uses windows;
function TMotor.GetNominal:word;
begin
result:=1000; { номинальная частота вращения }
end;
function TMotor.GetDr:TDir;
begin
result:=direction
end;
CONSTRUCTOR TMotor.Create(pp:TP);
BEGIN
INHERITED Create;
p:=pp;
RPM:=0;
Power:=0;
br:=false;
Direction:=CLOCKWISE
END;
DESTRUCTOR TMotor.Free;
BEGIN
END;
function TMotor.GetRPM:word;
begin
result:=rpm
end;
FUNCTION TMotor.GetPower:REAL;
BEGIN
GetPower:=формула для вычисления потребляемой мощности
END;
PROCEDURE TMotor.Start;
BEGIN
br:=false; { для пуска после аварии }
Feed(Nominal)
END;
PROCEDURE TMotor.Stop;
BEGIN
Feed(0)
END;
PROCEDURE TMotor.Feed(newRPM:WORD);
VAR i:WORD;
s:INTEGER;
BEGIN
IF RPM-newRPM>0 THEN
s:=-1
ELSE
s:=1;
FOR i:=1 TO ABS(RPM-newRPM) DO
BEGIN
if br then exit;
RPM:=RPM+s;
Sleep(10);
p;
Power:=GetPower
END
END;
PROCEDURE TMotor.Flip(newd:TDir);
VAR oldRPM:WORD;
BEGIN
IF (RPM=0) or (direction=newd) THEN { мотор стоит? ничего не делаем }
EXIT;
oldRPM:=RPM; { запомнили текущую частоту }
Feed(0); { остановились }
Direction:=newd;
Feed(oldRPM) { установили прежнее значение частоты }
END;
PROCEDURE TMotor.Abort; { аварийный останов }
BEGIN
br:=true;
RPM:=0;
Power:=0;
p
END;
end.