Шрифт:
Другим примером переопределяемого метода является функция THourly.GetPayAmount, вычисляющая сумму выплат работающему на почасовой ставке. В действительности, каждый тип объекта TEmployee имеет свой метод GetPayAmount, так как тип работающего зависит от того, как производится расчет. Метод THourly.GetPayAmount должен учитывать, сколько часов работал сотрудник, были ли сверхурочные работы, каков коэффициент увеличения за сверхурочные работы и т. д.
Метод TSalaried. GetPayAmount должен лишь делить ставку работающего на число выплат в каждом году (в нашем примере).
unit Workers;
interface
const
PayPeriods = 26; {в год}
OvertimeThreshold = 80; {за каждый период оплаты}
OvertimeFactor =1.5; {увеличение против обычной оплаты}
type
TEmployee = object
Name, Title: string[25];
Rate: Real;
procedure Init (AName, ATitle: string; ARate: Real);
function GetName : String;
function GetTitle : String;
function GetRate : Real;
function GetPayAmount : Real;
end;
THourly = object(TEmployee)
Time: Integer;
procedure Init(AName, ATitle: string; ARate:
Real, Atime: Integer);
function GetPayAmount : Real;
function GetTime : Real;
end;
TSalaried = object(TEmployee)
function GetPayAmount : Real;
end;
TCommissioned = object(TSalaried)
Commission : Real;
SalesAmount : Real;
constructor Init (AName, ATitle: String; ARate,
ACommission, ASalesAmount: Real);
function GetPayAmount : Real;
end;
implementation
function RoundPay(Wages: Real) : Real;
{округляем сумму выплат, чтобы игнорировать суммы меньше
денежной единицы}
begin
RoundPay := Trunc(Wages * 100) / 100;
.
.
.
TEmployee является вершиной нашей иерархии объектов и содержит первый метод GetPayAmount.
function TEmployee.GetPayAmount : Real;
begin
RunError(211); { дать ошибку этапа выполнения }
end;
Может вызвать удивление тот факт, что метод дает ошибку времени выполнения. Если вызывается Employee.GetPayAmount, то в программе возникает ошибка. Почему? Потому что TEmployee является вершиной нашей иерархии объектов и не определяет реального рабочего; следовательно, ни один из методов TEmployee не вызывается определенным образом, хотя они и могут быть наследованными. Все наши работники являются либо почасовиками, либо имеют оклады, либо работают на сдельщине. Ошибка на этапе выполнения прекращает выполнение программы и выводит 211, что соответствует сообщению об ошибке, связанной с вызовом абстрактного метода (если программа по ошибке вызывает TEmployee.GetPayAmount).
Ниже приводится метод THourly.GetPayAmount, в котором учитываются такие вещи, как сверхурочная оплата, число отработанных часов и т. д.
function THourly.GetPayAMount : Real;
var
OverTime: Integer;
begin
Overtime := Time – OvertimeThreshold;
if Overtime > 0 then
GetPayAmount := RoundPay(OvertimeThreshold * Rate +
Rate OverTime * OvertimeFactor * Rate)
else
GetPayAmount := RoundPay(Time * Rate)
end;
Метод TSalaried.GetPayAmount намного проще; в нем ставка
делится на число выплат:
function TSalaried.GetPayAmount : Real;
begin
GetPayAmount := RoundPay(Rate / PayPeriods);
end;
Если взглянуть на метод TCommissioned.GetPayAmount, то будет видно, что он вызывает TSalaried.GetPayAmount, вычисляет комиссионные и прибавляет их к величине, возвращаемой методом TSalaried. GetPayAmount.
function TСommissioned.GetPayAmount : Real;
begin
GetPayAmount := RoundPay(TSalaried.GetPayAmount +
Commission * SalesAmount);
end;
Важное замечание: хотя методы могут быть переопределены, поля данных переопределяться не могут. После того как было определено поле данных в иерархии объекта, никакой дочерний тип не может определить поле данных в точности с таким же именем.
3. Совместимость типов объектов
Наследование до некоторой степени изменяет правила совместимости типов в Borland Pascal. Помимо всего прочего, порожденный тип наследует совместимость типов всех своих порождающих типов.
Эта расширенная совместимость типов принимает три формы:
1) между реализациями объектов;
2) между указателями на реализации объектов;
3) между формальными и фактическими параметрами.
Однако очень важно помнить, что во всех трех формах совместимость типов расширяется только от потомка к родителю. Другими словами, дочерние типы могут свободно использоваться вместо родительских, но не наоборот.
Например, TSalaried является потомком TEmployee, а ТСош-missioned – потомком TSalaried. Помня об этом, рассмотрим следующие описания:
tyрe
PEmрloyee = ^TEmployee;
PSalaried = ^TSalaried;
PCommissioned = ^TCommissioned;
var
AnEmрloyee: TEmployee;
ASalaried: TSalaried;
PCommissioned: TCommissioned;
TEmployeePtr: PEmрloyee;
TSalariedPtr: PSalaried;
TCommissionedPtr: PCommissioned;
При данных описаниях справедливы следующие операторы
присваивания:
AnEmрloyee :=ASalaried;
ASalaried := ACommissioned;
TCommissionedPtr := ACommissioned;
Примечание
Порождающему объекту можно присвоить экземпляр любого из его порожденных типов. Обратные присваивания недопустимы.
Эта концепция является новой для Pascal, и вначале, возможно, трудновато запомнить, в каком порядке следует совместимость типов. Необходимо думать следующим образом: источник должен быть в состоянии полностью заполнить приемник. Порожденные типы содержат все, что содержат их порождающие типы благодаря свойству наследования. Поэтому порожденный тип имеет либо в точности такой же размер, либо (что чаще всего и бывает) он больше своего родителя, но никогда не бывает меньше. Присвоение порождающего (родительского) объекта порожденному (дочернему) могло бы оставить некоторые поля порожденного объекта неопределенными, что опасно и поэтому недопустимо.