我在弄清楚这种继承的情况时遇到了一些麻烦。
在我的班级TBalans
中,我有一个例程Initialiseer
,它将TBalPar
个对象作为参数。 TBalPar
是TNewBalPar
的祖先类,其中包含其他字段。现在我想从我的TBalans
课程中获取其他字段。我仍然可以将TNewBalPar
对象提供给Initialiseer
例程,但是如何获取后代类的数据呢?
我尝试了以下内容:我也将TBalans
导出到TNieuweBalans
,为其添加了新的附加字段,并在例程中分配它们:
type
TBalPar = class
//some vars
end;
TNewBalPar = class(TBalPar)
ExtraVar: TValue;
end;
TBalans = class
MyBalPar: TBalPar;
function Initialiseer(ABalPar: TBalPar): Boolean; virtual;
end;
TNieuweBalans = class(TBalans)
MyBalPar: TNewBalpar; //declared again so I don't need to cast it when using it
MyExtraVar: TValue;
function Initialiseer(ABalPar: TBalPar): Boolean; override;
end;
function TBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
MyBalPar := ABalPar;
end;
function TNieuweBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
inherited;
MyBalPar := TNewBalPar(ABalPar);
MyExtraVar := MyBalPar.ExtraVar; //instead of casting TNewBalPar(MyBalPar).ExtraVar
end;
此代码有效,但感觉不对:我声明了MyBalPar
字段两次。我想改进它。
请注意,我不是在寻找如何向外界展示ExtraVar
的方法,而是如何在TNieuweBalans
内方便地使用它。
如何消除双MyBalPar
字段,但仍然可以防止频繁的类型转换?
答案 0 :(得分:2)
对于祖先字段的方便指定的派生字段类型的需要不是禁止的,也不是罕见的。但是你的实现,就像你已经意识到的那样,有一些问题:
TBalans.MyBalPar
和TNieuweBalans.MyBalPar
,TNieuweBalans.MyBalPar.ExtraVar
和TNieuweBalans.MyExtraVar
,TBalPar
对象提供给TNieuweBalans.Initialiseer
会导致访问冲突,因为MyBalPar.ExtraVar
不存在。有多种方法可以解决这些问题。
防止额外字段的最基本的解决方案是使用从继承类中提取值的getter为它们提供属性(为了易于理解,我重命名了一些类型和变量):
type
TBalPar = class(TObject)
// some variables
end;
TBalParEx = class(TBalPar)
private
FExtra: TValue;
public
property Extra: TValue read FExtra write FExtra;
end;
TBalance = class(TObject)
private
FBalPar: TBalPar;
public
function Initialize(ABalPar: TBalPar): Boolean; virtual;
property BalPar: TBalPar read FBalPar;
end;
TBalanceEx = class(TBalance)
private
function GetExtra: TValue;
procedure SetExtra(Value: TValue);
public
function BalPar: TBalParEx;
function Initialize(ABalPar: TBalPar): Boolean; override;
property Extra: TValue read GetExtra write SetExtra;
end;
function TBalanceEx.BalPar: TBalParEx;
begin
Result := TBalParEx(inherited BalPar);
end;
function TBalanceEx.GetExtra: TValue;
begin
Result := BalPar.Extra;
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
BalPar.Extra := Value;
end;
使用这种方法,只需要一个类型转换,并且不需要额外的存储。
要强制TBalanceEx.BalPar
为TBalParEx
类型,您可以在Initialize
例程中引发异常:
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := True;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
if ABalPar is TBalParEx then
Result := inherited Initialize(ABalPar)
else
raise Exception.Create('Wrong BalPar type');
end;
当然,这要求正确的类功能对要求始终在其他类成员的任何其他使用之前调用Initialize
例程。由于这显然是初始化的目的,你可以忽略它,但可以添加防止误用的保护,如:
TBalance = class(TObject)
protected
function HasBalPar: Boolean; virtual;
...
TBalanceEx = class(TBalance)
protected
function HasBalPar: Boolean; override;
...
function TBalance.HasBalPar: Boolean;
begin
Result := FBalPar is TBalPar;
end;
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := HasPalBar;
end;
function TBalanceEx.GetExtra: TValue;
begin
if HasBalPar then
Result := BalPar.Extra
else
Result := nil;
end;
function TBalanceEx.HasBalPar: Boolean;
begin
Result := BalPar is TBalParEx;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
Result := inherited Initialize(ABalPar);
if Result = False then
raise Exception.Create('Initialization went wrong');
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
if HasBalPar then
BalPar.Extra := Value;
end;
反过来,这要求不要忘记为每个派生类实现HasBalPar
。您可以通过以下方式“保护”:
TBalance = class(TObject)
strict private
function HasBalPar: Boolean;
private
...
TBalanceEx = class(TBalance)
strict private
function HasBalPar: Boolean;
private
...
总而言之,要使其成为一个强大的设计需要一些工作。而您当前的方法提出了一个问题,即为什么您希望在Extra
类中拥有TBalanceEx
字段。甚至为什么要有一个TBalanceEx
课程。
从你的类的命名,我假设你有以下等价物:一个结构参数,如构建日期,所有者,位置,你有一个专门的结构,比如一个城堡,有额外的参数,如数量塔和是否有护城河:
您需要回答的问题是,结构是否需要知道它是城堡,还是宫殿,仓库,生物或化学结构。假设您的程序演变为能够处理所有不同类型的结构,那么您总是必须在程序中添加两个类,从而导致越来越复杂和即兴的设计,最终会让您遇到麻烦,如果不是已经。挑战在于使其成为一种更为通用和抽象的设计。
例如:
TStructureData
和TStructure
是否为单独的类?GetFeatures
类添加TStructureData
例程,那么TStructure
类可以在不知道它是城堡的情况下请求TCastle
的功能。大胆思考。