Каков наилучший подход к наследованию методов от классов, которые выходят после проверки?

Что в Delphi/Lazarus/FreePascal является лучшим способом унаследовать проверку родительского метода, которая выходит из метода? Допустим следующие классы и методы:

type

  TPlant = class
  public
    FIsGreen: Boolean;
    procedure DoPhotosynthesis; virtual;
  end;

  TChildPlant = class(TPlant)
  public
    procedure DoPhotosynthesis; override;
  end;

Implementation

{TPlant}
procedure TPlant.DoPhotosynthesis;
begin
  if not FIsGreen then Exit; //TPlants cannot do Photosynthesis if they are not green;

  //basic photosynthesis implementation not to be included in child plants
end;

Следующая реализация полностью скроет унаследованную проверку и/или повторит код.

{TChildPlant}
procedure TChildPlant.DoPhotosynthesis;
begin
  if not FIsGreen then Exit; //TPlant descendants cannot do Photosynthesis if they are not green;

  //photosynthesis implementation...
end;

Является ли создание другого метода, скажем, DoSpecificPhotosynthesis, и переопределение его лучшим способом реализации TChildPlant.DoPhotosynthesis, который действительно проверяет not FIsGreen и завершает работу, но не включает базовую реализацию фотосинтеза? (Смотрите ниже)

type

  TPlant = class
  public
    IsGreen: Boolean;
    procedure DoPhotosynthesis; virtual;
    procedure DoSpecificPhotosynthesis: virtual;
  end;

  TChildPlant = class(TPlant)
  public
    procedure DoSpecificPhotosynthesis; override;
  end;

Implementation

{TPlant}
procedure TPlant.DoPhotosynthesis;
begin
  if not FIsGreen then Exit; //TPlants cannot do Photosynthesis if they are not green;

  //photosynthesis implementation (child plants must implement their specific way);
  DoSpecificPhotosynthesis;
end;

{TChildPlant}
procedure TChildPlant.DoSpecificPhotosynthesis;
begin
  //photosynthesis implementation...
end;

Любые другие мысли?


person EMBarbosa    schedule 30.10.2013    source источник
comment
Лично я бы выбрал подход, который вы предлагаете, но сделал DoSpecificPhotosynchronous защищенным. Кроме того, вместо того, чтобы делать DoPhoto Synthetic виртуальным, вы можете абстрагировать тест на достоверность в виртуальный метод CanDoPhoto Synthese.   -  person Chris Rolliston    schedule 30.10.2013
comment
@ChrisRolliston Я не включал директивы protected или private только для упрощения. Но вы правы. По моему мнению, мне вообще не нужно, чтобы DoPhotosynthesis было virtual. В любом случае, это никогда не будет переопределено.   -  person EMBarbosa    schedule 30.10.2013


Ответы (2)


Избегайте наследования каждого поведения с помощью шаблона проектирования стратегии, например следующего:
Таким образом, вам не нужно несколько версий TPlant, только несколько версий поведения.

program Strategy;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TPhotosystesisBehavior = class
  public
    procedure DoPhotosyntesis; virtual; abstract;
  end;

  TGreenPhotosyntesisBehavior = class(TPhotosystesisBehavior)
  public
    procedure DoPhotosyntesis; override;
  end;

  TOtherPhotosynthesisBehavior = class(TPhotosystesisBehavior)
  public
    procedure DoPhotosyntesis; override;
  end;

  TPlant = class
  private
    function GetPhotoBehavior: TPhotosystesisBehavior;
    procedure SetPhotoBehavior(const Value: TPhotosystesisBehavior);
  protected
    FPhotoBehavior: TPhotosystesisBehavior;
  public
    procedure PerformPhotosyntesis;
    property PhotoBehavior: TPhotosystesisBehavior read GetPhotoBehavior write SetPhotoBehavior;
  end;


{ TGreenPhotosyntesisBehavior }

procedure TGreenPhotosyntesisBehavior.DoPhotosyntesis;
begin
  Writeln('  - Eating some solar energy, delicious!!');
end;

{ TPlant }

function TPlant.GetPhotoBehavior: TPhotosystesisBehavior;
begin
  Result:= FPhotoBehavior;
end;

procedure TPlant.PerformPhotosyntesis;
begin
  Writeln('Performing Photosynthesis: ');
  if Assigned(FPhotoBehavior) then
    FPhotoBehavior.DoPhotosyntesis;
  Writeln('Performing Photosynthesis: End');    
end;

procedure TPlant.SetPhotoBehavior(const Value: TPhotosystesisBehavior);
begin
  FPhotoBehavior := Value;
end;

{ TOtherPhotosynthesisBehavior }

procedure TOtherPhotosynthesisBehavior.DoPhotosyntesis;
begin
  Writeln('  - I Do not like Solar Enery! ');
end;

procedure TestGreenPlant;
var Plant: TPlant;
    GreenPlantBehavior: TGreenPhotosyntesisBehavior;
begin
  Writeln('TestGreenPlant: ');
  Writeln('');
  Plant := TPlant.Create;
  Plant.PerformPhotosyntesis;
  Writeln('');
  GreenPlantBehavior:= TGreenPhotosyntesisBehavior.Create;
  Plant.PhotoBehavior := GreenPlantBehavior;
  Plant.PerformPhotosyntesis;
  Writeln('');
  Writeln('TestGreenPlant: End');
  Writeln('');    
end;

procedure TestOtherPlant;
var Plant: TPlant;
    OtherPlantBehavior: TOtherPhotosynthesisBehavior;
begin
  Writeln('TestOtherPlant: ');
  Writeln('');
  Plant := TPlant.Create;
  Plant.PerformPhotosyntesis;
  Writeln('');
  OtherPlantBehavior:= TOtherPhotosynthesisBehavior.Create;
  Plant.PhotoBehavior := OtherPlantBehavior;
  Plant.PerformPhotosyntesis;
  Writeln('');
  Writeln('TestOtherPlant: End ');
  Writeln('');
end;

begin
  TestGreenPlant;
  Writeln('--------------');
  TestOtherPlant;
  Readln;
end.

ОБНОВЛЕНИЕ:

При желании вы также можете комбинировать этот шаблон с фабрикой, чтобы определить, какое поведение вы хотите использовать в каждом типе. В приведенном ниже коде есть 3 перегруженные функции для получения экземпляра TPlant, вам не нужны все они, они здесь только для демонстрации:

program Strategy;

{$APPTYPE CONSOLE}

uses
  SysUtils, TypInfo;

type
  TPhotosystesisBehavior = class
  public
    procedure DoPhotosyntesis; virtual; abstract;
    function ToString: String; virtual;
  end;

  TGreenPhotosyntesisBehavior = class(TPhotosystesisBehavior)
  public
    procedure DoPhotosyntesis; override;
    function ToString: String; override;
  end;

  TOtherPhotosynthesisBehavior = class(TPhotosystesisBehavior)
  public
    procedure DoPhotosyntesis; override;
    function ToString: String; override;
  end;

  TBehaviorType = class of TPhotosystesisBehavior;
  TEnumBehavior = (GreenPlant, OtherPlant, Unknown);

  TPlant = class
  private
    function GetPhotoBehavior: TPhotosystesisBehavior;
    procedure SetPhotoBehavior(const Value: TPhotosystesisBehavior);
  protected
    FPhotoBehavior: TPhotosystesisBehavior;
  public
    procedure PerformPhotosyntesis;
    property PhotoBehavior: TPhotosystesisBehavior read GetPhotoBehavior write SetPhotoBehavior;
  end;

  TPlantFactory = class
  private
    class function InternalGetPlantTyppedInstance(ABehavior: TPhotosystesisBehavior): TPlant; 
  public
    class function GetPlantTyppedInstance(AType: String): TPlant; overload;
    class function GetPlantTyppedInstance(AType: TBehaviorType): TPlant; overload;
    class function GetPlantTyppedInstance(AType: TEnumBehavior): TPlant; overload;
  end;


{ TGreenPhotosyntesisBehavior }

procedure TGreenPhotosyntesisBehavior.DoPhotosyntesis;
begin
  Writeln('  - Eating some solar energy, delicious!!');
end;

function TGreenPhotosyntesisBehavior.ToString: String;
begin
  Result:= 'TGreenPhotosyntesisBehavior';
end;

{ TPlant }

function TPlant.GetPhotoBehavior: TPhotosystesisBehavior;
begin
  Result:= FPhotoBehavior;
end;

procedure TPlant.PerformPhotosyntesis;
begin
  Writeln('Performing Photosynthesis: ');
  if Assigned(FPhotoBehavior) then
    FPhotoBehavior.DoPhotosyntesis;
  Writeln('Performing Photosynthesis: End');    
end;

procedure TPlant.SetPhotoBehavior(const Value: TPhotosystesisBehavior);
begin
  FPhotoBehavior := Value;
end;

{ TOtherPhotosynthesisBehavior }

procedure TOtherPhotosynthesisBehavior.DoPhotosyntesis;
begin
  Writeln('  - I Do not like Solar Enery! ');
end;

procedure TestGreenPlant;
var Plant: TPlant;
    GreenPlantBehavior: TGreenPhotosyntesisBehavior;
begin
  Writeln('TestGreenPlant: ');
  Writeln('');
  Plant := TPlant.Create;
  Plant.PerformPhotosyntesis;
  Writeln('');
  GreenPlantBehavior:= TGreenPhotosyntesisBehavior.Create;
  Plant.PhotoBehavior := GreenPlantBehavior;
  Plant.PerformPhotosyntesis;
  Writeln('');
  Writeln('TestGreenPlant: End');
  Writeln('');    
end;

procedure TestOtherPlant;
var Plant: TPlant;
    OtherPlantBehavior: TOtherPhotosynthesisBehavior;
begin
  Writeln('TestOtherPlant: ');
  Writeln('');
  Plant := TPlant.Create;
  Plant.PerformPhotosyntesis;
  Writeln('');
  OtherPlantBehavior:= TOtherPhotosynthesisBehavior.Create;
  Plant.PhotoBehavior := OtherPlantBehavior;
  Plant.PerformPhotosyntesis;
  Writeln('');
  Writeln('TestOtherPlant: End ');
  Writeln('');
end;

function TOtherPhotosynthesisBehavior.ToString: String;
begin
  Result:= 'TOtherPhotosynthesisBehavior';
end;

{ TPlantFactory }

class function TPlantFactory.GetPlantTyppedInstance(
  AType: TBehaviorType): TPlant;
var Behavior : TPhotosystesisBehavior;
begin
  Writeln('GetPlantTyppedInstance (TBehaviorType): ');
  Writeln('');
  Behavior := AType.Create;
  Result := InternalGetPlantTyppedInstance(Behavior);
  Writeln('');
  Writeln('  - GetPlantTyppedInstance (TBehaviorType): Type Created ');
  Writeln('');
  Writeln('GetPlantTyppedInstance (TBehaviorType): End');
  Writeln('');
end;

class function TPlantFactory.GetPlantTyppedInstance(
  AType: String): TPlant;
begin
  Writeln('GetPlantTyppedInstance (String): ');
  Writeln('');
  if AType = 'GreenPlant' then
    Result := GetPlantTyppedInstance(TGreenPhotosyntesisBehavior)
  else if AType = 'OtherPlant' then
    Result := GetPlantTyppedInstance(TOtherPhotosynthesisBehavior)
  else
    raise Exception.Create('Unkown Type');
  Writeln('');
  Writeln('GetPlantTyppedInstance (String): End');
  Writeln('');
end;

class function TPlantFactory.InternalGetPlantTyppedInstance(
  ABehavior: TPhotosystesisBehavior): TPlant;
begin
  Writeln('GetPlantTyppedInstance (TPhotosystesisBehavior): ');
  Writeln('');
  Result := TPlant.Create;
  Result.PhotoBehavior := ABehavior;
  Writeln('');
  Writeln('GetPlantTyppedInstance (TPhotosystesisBehavior): Plant Created, Type: '+ABehavior.ToString);
  Writeln('');
  Writeln('GetPlantTyppedInstance (TPhotosystesisBehavior): End');
  Writeln('');
end;

class function TPlantFactory.GetPlantTyppedInstance(AType: TEnumBehavior): TPlant;
begin
  Writeln('GetPlantTyppedInstance (TEnumBehavior): ');
  Writeln('');
  Result := GetPlantTyppedInstance( GetEnumName(TypeInfo(TEnumBehavior) , Ord(AType)) );
  Writeln('GetPlantTyppedInstance (TEnumBehavior): End');
  Writeln('');
end;

{ TPhotosystesisBehavior }

function TPhotosystesisBehavior.ToString: String;
begin
  Result:= 'TPhotosystesisBehavior';
end;

begin
  TestGreenPlant;
  Writeln('--------------');
  TestOtherPlant;

  Writeln('--------------');
  Writeln('Factory: ');

  Writeln('- Green: ');

  TPlantFactory.GetPlantTyppedInstance('GreenPlant');
  TPlantFactory.GetPlantTyppedInstance(GreenPlant);
  TPlantFactory.GetPlantTyppedInstance(TGreenPhotosyntesisBehavior);

  Writeln('');

  Writeln('- Other: ');

  TPlantFactory.GetPlantTyppedInstance('OtherPlant');
  TPlantFactory.GetPlantTyppedInstance(OtherPlant);
  TPlantFactory.GetPlantTyppedInstance(TOtherPhotosynthesisBehavior);

  Readln;
end.

ВАЖНО: все это становится шаблонным, если используется низкоуровневое наследование или очень простые проекты. Вы должны решить, стоит ли пытаться или нет

person EProgrammerNotFound    schedule 30.10.2013
comment
Что меня особенно беспокоит, так это то, что часть if not FIsGreen then Exit; перекодируется снова и снова или просто скрыта. В вашем ответе мне пришлось бы добавить этот проверочный код в TGreenPhotosyntesisBehavior.DoPhotosyntesis и в TOtherPhotosynthesisBehavior.DoPhotosyntesis, поэтому я думаю, что снова попаду в проблему. Нет? - person EMBarbosa; 30.10.2013
comment
Но я согласен, есть солнечную энергию вкусно!! :D - person EMBarbosa; 30.10.2013
comment
@EMBarbosa Нет, эта проблема больше никогда не возникнет, обратите внимание, вы инкапсулировали поведение, вам больше не нужно это проверять, просто добавьте поведение фотосинтеза в экземпляр TPlant, когда вам нужно, и другое поведение, когда вам это не нужно - person EProgrammerNotFound; 30.10.2013
comment
Опс. Я понял. Вы не включили эту часть, поэтому я был сбит с толку. На самом деле я привык к этому больше, чем я думал. Большинство событий классов в Delphi определяются примерно так. - person EMBarbosa; 30.10.2013

Этот простой случай можно легко решить, превратив этот метод в функцию:

type
  TPlant = class(TObject)
  private
    FIsGreen: Boolean;
  protected
    function DoPhotosynthesis: Boolean; virtual;
  end;

  TChildPlant = class(TPlant)
  protected
    function DoPhotosynthesis: Boolean; override;
  end;

implementation

{TPlant}

function TPlant.DoPhotosynthesis: Boolean;
begin
  Result := FIsGreen;
  if Result then
    //Do basic photosynthesis
end;

{ TChildPlant }

function TChildPlant.DoPhotosynthesis: Boolean;
begin
  Result := inherited DoPhotosynthesis;
  if Result then
    //Do specific photosynthesis
end;

Но более сложные конструкции могут выиграть от реализации шаблона проектирования, такого как Strategy.

В любом случае, вы должны спросить себя, принадлежит ли FIsGreen классу TPlant. Если различное поведение может быть разделено на несколько классов, то я бы: Сделал net hesisstate, чтобы ввести еще один класс между цепочкой наследования.

person NGLN    schedule 30.10.2013