4

この要件に従って、追加機能を使用してクラス階層を拡張する方法を考えています。1) 元の階層に触れることができない 2) 新しい機能を別のユニットに開発する必要がある

たとえば、uClasses.pasユニットの次のクラス階層を考えてみましょう。

TBaseClass = class
  ID : Integer;
  Name : String;
end;

TDerivedClass = class(TBaseClass)
  Age : Integer
  Address : String
end;

テキストに保存するなど、他の機能をクラスにアタッチしたい (これは単なる例です)。そこで、次のユニットuClasses_Text.pasを想像しました。

uses uClasses;

Itextable = interface
  function SaveToText: String;
end;

TBaseClass_Text = class(TBaseClass, Itextable)
  function SaveToText: String;
end;

TDerivedClass_Text = class(TDerivedClass, ITextable)
  function SaveToText: String;
end;

function TBaseClass_Text.SaveToText: String;
begin
  result := Self.ID + ' ' + Self.Name;
end;

function TDerivedClass_Text.SaveToText: String;
begin
  // SaveToText on derived class must call SaveToText from the "BaseClass" and then append its additional fields  
  result := ???? // Call to TBaseClass_Text.SaveToText. Or better, ITextable(Self.ParentClass).SaveToText;
  result := result + Self.Age + ' ' + Self.Address;
end;

TDerivedClass_Text.SaveToText 内から SaveToText の「基本」実装を参照するにはどうすればよいですか? たぶん、何らかの方法でインターフェースを処理していますか?

または、このケースに対するより適切でクリーンなアプローチはありますか?

ありがとう、

4

3 に答える 3

4

David が指摘したように、存在しない基本クラスのメソッドを参照することはできません。

クラス ヘルパーを使用すると、質問を別の方法で解決できます。最初のクラスのヘルパーは関数をTBaseClassHelper追加し、2 番目のクラスのヘルパーも同様に追加します。この 2 番目の関数の実装を見てください。を呼び出します。SaveToTextTDerivedClassHelperSaveToTextinherited SaveToText

更新 2

SaveToOP は、実装ごとに個別のユニットを必要としていました。David と Arioch によるコメントの助けを借りて、クラス ヘルパーは他のクラス ヘルパーから継承できることがわかりました。完全な例を次に示します。

unit uClasses;

type    

  TBaseClass = class
    ID: Integer;
    Name: String;
  end;

  TDerivedClass = class(TBaseClass)
    Age: Integer;
    Address: String;
  end;

unit uClasses_Text;

uses uClasses,uClasses_SaveToText,uClasses_SaveToIni,uClasses_SaveToDB;

type    
  ITextable = interface
    function SaveToText: string;
    function SaveToIni: string;
    function SaveToDB: string;
  end;

  // Adding reference counting through an interface, since multiple inheritance
  // is not possible (TInterfacedObject and TBaseClass) 
  TBaseClass_Text = class(TBaseClass, IInterface, ITextable)
  strict private
    FRefCount: Integer;
  protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

  TDerivedClass_Text = class(TDerivedClass, IInterface, ITextable)
  strict private
    FRefCount: Integer;
  protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;    

implementation

uses Windows;

function TBaseClass_Text.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TBaseClass_Text._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TBaseClass_Text._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;    

function TDerivedClass_Text.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TDerivedClass_Text._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;    

function TDerivedClass_Text._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

unit uClasses_SaveToText;

interface

uses uClasses;

type    
  TBaseClassHelper = class helper for TBaseClass
    function SaveToText: string;
  end;

  TDerivedClassHelper = class helper for TDerivedClass
    function SaveToText: string;
  end;

implementation

function TBaseClassHelper.SaveToText: string;
begin
  Result := 'BaseClass Text info';
end;

function TDerivedClassHelper.SaveToText: string;
begin
  Result := inherited SaveToText;
  Result := Result + ' DerivedClass Text info';
end;

unit uClasses_SaveToIni;

interface

Uses uClasses,uClasses_SaveToText;

type    
  TBaseClassHelperIni = class helper(TBaseClassHelper) for TBaseClass
    function SaveToIni: string;
  end;

  TDerivedClassHelperIni = class helper(TDerivedClassHelper) for TDerivedClass
    function SaveToIni: string;
  end;

implementation

function TBaseClassHelperIni.SaveToIni: string;
begin
  Result := 'BaseClass Ini info';
end;

function TDerivedClassHelperIni.SaveToIni: string;
begin
  Result := inherited SaveToIni;
  Result := Result + ' DerivedClass Ini info';
end;

unit uClasses_SaveToDB;

interface

Uses uClasses,uClasses_SaveToText,uClasses_SaveToIni;

Type    
  TBaseClassHelperDB = class helper(TBaseClassHelperIni) for TBaseClass
    function SaveToDB: string;
  end;

  TDerivedClassHelperDB = class helper(TDerivedClassHelperIni) for TDerivedClass
    function SaveToDB: string;
  end;

implementation

function TBaseClassHelperDB.SaveToDB: string;
begin
  Result := 'BaseClass DB info';
end;

function TDerivedClassHelperDB.SaveToDB: string;
begin
  Result := inherited SaveToDB;
  Result := Result + 'DerivedClass DB info';
end;

program TestClasses;

uses
  uClasses in 'uClasses.pas',
  uClasses_Text in 'uClasses_Text.pas',
  uClasses_SaveToText in 'uClasses_SaveToText.pas',
  uClasses_SaveToIni in 'uClasses_SaveToIni.pas',
  uClasses_SaveToDB in 'uClasses_SaveToDB.pas';
var
  Textable: ITextable;
begin
  Textable := TDerivedClass_Text.Create;
  WriteLn(Textable.SaveToText);
  WriteLn(Textable.SaveToIni);
  WriteLn(Textable.SaveToDB);
  ReadLn;
end.

更新 1

のいくつかの側面を実装する必要性についてのコメントを読んでSaveToText、単純なピギーバック ソリューションを提案します。

type
  ITextable = interface
    function SaveToText: String;
  end;
  TMyTextGenerator = class(TInterfacedObject,ITextable)
  private
    Fbc : TBaseClass;
  public
    constructor Create( bc : TBaseClass);
    function SaveToText: String;
  end;

{ TMyTextGenerator }

constructor TMyTextGenerator.Create(bc: TBaseClass);
begin
  Inherited Create;
  Fbc := bc;
end;

function TMyTextGenerator.SaveToText: String;
begin
  Result := IntToStr(Fbc.ID) + ' ' + Fbc.Name;
  if Fbc is TDerivedClass then
  begin
    Result := Result + ' ' + IntToStr(TDerivedClass(Fbc).Age) + ' ' +
      TDerivedClass(Fbc).Address;
  end;
end;

TSaveToIni、TSaveToDB などを同じパターンで別のユニットに実装します。

于 2012-08-09T12:56:58.003 に答える
1

...によると、誠実さは過大評価されています (歌を思い出せません)。私たちの多くは継承を過大評価しており、多くの場合、構成や委譲よりも継承の問題を迅速に解決しすぎると思います。

ファイルに永続化できるようにしたいすべてのクラスに SaveToFile メソッドを追加したいという欲求には本当に疑問があります。

私の見解では、クラスはその存在理由ではない責任について無知であるべきです。持続性はそのような責任の 1 つであり、印刷は別の責任です。印刷クラスは、印刷を担当する必要があります。もちろん、印刷クラスを、印刷したい認識可能なすべてのクラスを処理する if ステートメントの hornets ネットにしたくないでしょう。したがって、Printer 基本クラスを定義し、それを PeoplePrinter、LocationPrinter、WhateverPrinter の子孫で拡張します。それぞれがクラス階層全体を処理できます。

今デコレータのパターンを考えているなら、良い、よく見つけた。

既存の階層の子孫を作成するのではなく、特定の責任のためにクラスと、場合によってはクラス階層を作成するという考え方です。を呼び出す代わりに、既存のクラスのインスタンスを保存する場合は、SomeClass.SaveToTextをインスタンス化し、TSaver保存するクラスのインスタンスに渡します。

非常に素朴な実装は次のようになります。

type
  TSaver = class(TObject)
    procedure SaveToText; virtual; abstract;
  end;

  TBaseHierarchySaver = class(TSaver)
  private
    FBase: TBaseClass;
  public
    constructor Create(aBase: TBaseClass);
    procedure SaveToText; override;

    class procedure Save(aBase: TBaseClass);
  end;

constructor TBaseHierarchySaver.Create(aBase: TBaseClass);
begin
  FBase := aBase;
end;

class procedure TBaseHierarchySaver.Save(aBase: TBaseClass);
var
  Me: TSaver;
begin
  Me := TBaseHierarchySaver.Create(aBase);
  Me.SaveToText;
end;

procedure TBaseHierarchySaver.SaveToText;
var
  Str: TStrings;
begin
  Str := TStringList.Create;
  try
    Str.Add(Format('%s (%d)', [FBase.Name, FBase.ID]));
    if FBase.InheritsFrom(TDerivedClass) then
    begin
      Str.Add(Format('%d', [TDerivedClass(FBase).Age]));
      Str.Add(Format('%s', [TDerivedClass(FBase).Address]));
    end;
  finally
    Str.SaveToFile('SomeFileName');
    Str.Free;
  end;
end;

私はこれがあまり好きではありません。もろいです。もっとうまくやることができます。

上記のコードをより柔軟にしたり、多態的な実行を提供したりする方法はたくさんあります。たとえば、TSaver は、TBaseClass のクラスに関連付けられた匿名メソッドのディクショナリを持つことができます。次に、TSaver.SaveToText は TBaseClass 引数を取得し、渡されたインスタンスのクラスの各匿名メソッドを実行するように実装できます (匿名メソッドに関連付けられたクラスから継承する場合)。

type
  TBaseClassClass = class of TBaseClass;
  TAddInfoProc = reference to procedure(aBase: TBaseClass; aStr: TStrings);

  TSaver = class(TObject)
  class var
    FAddInfoClasses: TDictionary<TBaseClassClass, TAddInfoProc>;
  public
    class procedure RegisterAddInfoProc(aBase: TBaseClassClass; 
      aAddInfo: TAddInfoProc);

    class procedure SaveToText(aBase: TBaseClass);
  end;

TSaver.RegisterAddInfoProc(TBaseClass, procedure(aBase: TBaseClass; aStr: TStrings)
  begin
    aStr.Add(Format('%s (%d)', [aBase.Name, aBase.ID]));
  end
);

TSaver.RegisterAddInfoProc(TDerivedClass, procedure(aBase: TBaseClass; aStr: TStrings)
  begin
    aStr.Add(Format('%d', [TDerivedClass(FBase).Age]));
    aStr.Add(Format('%s', [TDerivedClass(FBase).Address]));
  end
);

これにより継承階層から解放されますが、ポリモーフィックな実行が必要な場合は、特定の TBaseClass 子孫を「AddInfo」子孫の一致する階層に結び付けるディクショナリに変更できます。各 AddInfo 子孫は独自の情報を追加します。

type
  TAddInfo = class(TObject)
  public
    procedure AddInfo(aBase: TBaseClass; aStr: TStrings); virtual;
  end;

  TDerivedAddInfo = class(TAddInfo)
  public
    procedure AddInfo(aBase: TBaseClass; aStr: TStrings); override;
  end;

procedure TAddInfo.AddInfo(aBase: TBaseClass; aStr: TStrings);
begin
  aStr.Add(Format('%s (%d)', [aBase.Name, aBase.ID]));
end;

procedure TDerivedAddInfo.AddInfo(aBase: TBaseClass; aStr: TStrings);
var
  Derived: TDerivedClass absolute aBase;
begin
  inherited;
  if not aBase.InheritsFrom(TDerivedClass) then Exit;

  aStr.Add(Format('%d', [Derived.Age]));
  aStr.Add(Format('%s', [Derived.Address]));
end;

type
  TBaseClassClass = class of TBaseClass;
  TAddInfoClass = class of TAddInfo;

  TSaver = class(TObject)
  class var
    FAddInfoClasses: TDictionary<TBaseClassClass, TAddInfoClass>;
  public
    class procedure RegisterAddInfoClass(aBase: TBaseClassClass; 
      aAddInfo: TAddInfoClass);

    class procedure SaveToText(aBase: TBaseClass);
  end;

ちなみに、これは他の場所で提案されているクラス ヘルパー メソッドと非常によく似ていますが、一度に 1 つのクラス ヘルパーしかアクティブにできないという制限はありません。したがって、TSaver、TPrinter、TMailer など、TBaseClass で実行したいことは何でもできますが、それは主な責任ではありません。

ところで、上記の絶対の使用は、私が耐えられる絶対の数少ない使用例の 1 つです。ハード キャストの便利なショート ハンドであり、早期終了の制約によって安全になります。これは、それ自体が、私が我慢できる早期終了の数少ないユース ケースの 1 つでもあります :-)

于 2012-08-09T21:03:25.293 に答える
1

Delphi はクラスの多重継承をサポートしていないため、次のような解決策を求められます。

function BaseClassSaveToText(obj: TBaseClass): string;
begin
  Result := IntToStr(obj.ID) + ' ' + obj.Name;
end;

function TBaseClass_Text.SaveToText: String;
begin
  Result := BaseClassSaveToText(Self);
end;

function DerivedClassSaveToText(obj: TDerivedClass): string;
begin
  Result := BaseClassSaveToText(obj) + IntToStr(obj.Age) + ' ' + obj.Address;
end;

function TDerivedClass_Text.SaveToText: String;
begin
  Result := DerivedClassSaveToText(Self);
end;

InキーワードDerivedClassSaveToTextを使用したいのinheritedですが、これら 2 つのクラスが必要な共通の祖先を共有していないため、使用できません。

更新: @LU RD は、クラス ヘルパーを使用してすべてを実行する方法を示しています。個人的には、クラス ヘルパーに少しアレルギーがあります。もちろん、ヘルパーを使用したくない理由は他にもあるかもしれません。たとえば、古いバージョンの Delphi を使用している場合、それらは存在しません。

于 2012-08-09T12:57:20.970 に答える