3

What is, in Delphi/Lazarus/FreePascal the best way to inherit parents method verification that exits from the method? Suppose the following classes and methods:

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;

The following implementation would totally hide inherited verification and/or repeat the code.

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

  //photosynthesis implementation...
end;

Is creating another method, say DoSpecificPhotosynthesis, and override it the best way to implement TChildPlant.DoPhotosynthesis that actually verify for not FIsGreen and exits but don't include the basic photosynthesis implementation? (See bellow)

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;

Any other thoughts?

EMBarbosa
  • 1,473
  • 1
  • 22
  • 76
  • 4
    Personally I'd take the approach you suggest, though making DoSpecificPhotosynthesis protected. Also, instead of making DoPhotosynthesis virtual, you could abstract the validity test into a virtual CanDoPhotosynthesis method. – Chris Rolliston Oct 30 '13 at 14:22
  • @ChrisRolliston I did not included `protected` or `private` directives just for simplification sake. But you are right. In my suggestion, I don't need that `DoPhotosynthesis` be `virtual` at all. It would never be overridden anyway. – EMBarbosa Oct 30 '13 at 17:01

2 Answers2

3

Avoid inheritance to each behavior by using Strategy Design pattern, like following:
This way, You don't need multiple TPlant versions, only multiple behavior ones.

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.

UPDATE:

If you prefer, you can also combine this pattern with a Factory to determine what behavior you want to use in each type. In the code below, there are 3 overloaded functions to retrieve an instance of TPlant, you don't need all of them, it is here to demonstration purpose only:

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.

IMPORTANT: All of this become boilerplate if there is low level inheritance or in very simple projects. You must decide if worth the shot or not

EProgrammerNotFound
  • 2,403
  • 4
  • 28
  • 59
  • What I'm specially worried about is the `if not FIsGreen then Exit;` part being recoded over and over or being simple hided. In your answer I would had to add this verification code in `TGreenPhotosyntesisBehavior.DoPhotosyntesis` and in `TOtherPhotosynthesisBehavior.DoPhotosyntesis`, so I think I would fall in the problem again. No? – EMBarbosa Oct 30 '13 at 16:52
  • But I agree, Eating solar energy is delicious!! :D – EMBarbosa Oct 30 '13 at 17:03
  • @EMBarbosa No, it will never be in the same problem again, note, you encapsulated the behavior, you never need to check that anymore, just add the photsynthesisbehavior to TPlant instance when you need, and other behavior when you don't need – EProgrammerNotFound Oct 30 '13 at 17:28
  • Ops. I got it. You did not included that part, so I was confused. Actually I'm used with this way more than I thought. Most of classes events in Delphi are defined almost like this. – EMBarbosa Oct 30 '13 at 17:34
2

This simple case can easily be solved by turning that method into a function:

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;

But more complex constructs may benefit from implementing a design patern like Strategy.

Anyway, you should ask yourself whether FIsGreen belongs to the TPlant class. If different behaviour can be devided over several classes, then I would: Do net hesistate to introduce one more class in between the inheritence chain.

Community
  • 1
  • 1
NGLN
  • 43,011
  • 8
  • 105
  • 200