如何使用祖先字段声明提供派生实例?

时间:2014-09-19 09:35:08

标签: delphi inheritance

我在弄清楚这种继承的情况时遇到了一些麻烦。

在我的班级TBalans中,我有一个例程Initialiseer,它将TBalPar个对象作为参数。 TBalParTNewBalPar的祖先类,其中包含其他字段。现在我想从我的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字段,但仍然可以防止频繁的类型转换?

1 个答案:

答案 0 :(得分:2)

当前设计

对于祖先字段的方便指定的派生字段类型的需要不是禁止的,也不是罕见的。但是你的实现,就像你已经意识到的那样,有一些问题:

  • doubled字段需要不必要的内存,
  • 您需要将更改同步到TBalans.MyBalParTNieuweBalans.MyBalPar
  • 您需要将更改同步到TNieuweBalans.MyBalPar.ExtraVarTNieuweBalans.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.BalParTBalParEx类型,您可以在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:Location,BuildDate
  • TCastleData:Location,BuildDate,TowerCount,HasMoat
  • TStructure:StructureData
  • TCastle:StructureData,CastleData

您需要回答的问题是,结构是否需要知道它是城堡,还是宫殿,仓库,生物或化学结构。假设您的程序演变为能够处理所有不同类型的结构,那么您总是必须在程序中添加两个类,从而导致越来越复杂和即兴的设计,最终会让您遇到麻烦,如果不是已经。挑战在于使其成为一种更为通用和抽象的设计。

例如:

  • 必须TStructureDataTStructure是否为单独的类?
  • 是否可以将特定数据的计算,分析或表述请求“外包”到特定类别?例如:如果您向GetFeatures类添加TStructureData例程,那么TStructure类可以在不知道它是城堡的情况下请求TCastle的功能。
  • ...

大胆思考。