Question

abstract class Animal {
  function eat() {..}
  function sleep() {..}
  function isSmart()
}

class Dog extends Animal {
  public $blnCanBark;
  function isSmart() {
    return $this->blnCanBark;
  }
}

class Cat extends Animal {
  public $blnCanJumpHigh;
  function isSmart() {
    return $this->blnCanJumpHigh;
  }
}

.. and so on up to 10-20 animals.

Now I created a factory using simple factory method and try to create instances like this:

class AnimalFactory {
  public static function create($strName) {
       switch($strName) {
          case 'Dog':
            return new Dog();
          case 'Cat':
            return new Cat();
          default:
            break;
       }
  }
}

The problem is I can't set the specific attributes like blnCanBark, blnCanJumpHigh in an efficient way.

I can send all of them as extra params to create but this will not scale to more then a few classes. Also I can't break the inheritance because a lot of the basic functionality is the same.

Is there a better pattern to solve this?

Was it helpful?

Solution

Factory combined with Strategy comes to mind. Each animal could support the notion of a set of behaviours that can be added / passed as an array of behaviour interface references or array of instances of descendants of an abstract behaviour class.

Don't think this is the way to go though, as it means you put the decision of which behaviours to pass in outside of the factory and that seems to defeat the purpose of having a factory in the first place.

Builder seems specifically suited to your problem. I have always viewed Builder as an extension (not in the inheritance but the "taking it one step further" sense). Instead of focusing on instantiation, it specifically caters to constructing instances of classes that need more than just a call to a constructor.

Update:

Below is an example of the Builder pattern. It uses Delphi syntax as that is the language I'm most familiar with, but I have refrained from using Delphi specific constructs (meta classes for example). Error checking and freeing your instances has also been kept out of the example.

Please note that instead of passing the director an instance of a builder, you could also pass a string and let the director work out which builder to instantiate and use. In that case I would opt for a registry of builders in the director and a register method on the director so you can declare and builders without changing the director.


type
  TBehaviour = class(TObject)
  end;
  TSpeakBehaviour = class(TBehaviour);
  TJumpBehaviour = class(TBehaviour);

  TAnimal = class(TObject)
  public
    procedure AddBehaviour(const aBehaviour: TBehaviour);
  end;

  TCat = class(TAnimal);
  TDog = class(TAnimal);

  TAnimalBuilder = class(TObject)
  public
    procedure BuildBody; virtual; abstract;
    procedure AddBehaviours; virtual; abstract;
    function GetAnimal: TAnimal; virtual; abstract;
  end;

  TCatBuilder = class(TBuilder)
  private
    FCat: TCat;
  public
    procedure BuildBody; override;
    procedure AddBehaviours; override;
    function GetAnimal: TAnimal; override;
  end;

  TDogBuilder = class(TBuilder)
  private
    FDog: TDog;
  public
    procedure BuildBody; override;
    procedure AddBehaviours; override;
    function GetAnimal: TAnimal; override;
  end;

  TAnimalDirector = class(TObject)
  private
    FBuilder: TAnimalBuilder;
  public
    constructor Create(const aBuilder: TAnimalBuilder);
    function BuildAnimal: TAnimal;
  end;

procedure BuildMeAnAnimal;    
var
  Builder: TAnimalBuilder;
  Director: TAnimalDirector;
  Animal: TAnimal;
begin
  Builder := TCatBuilder.Create;
  Director := TAnimalDirector.Create(Builder);
  Animal := Director.BuildAndimal;
end;

//--- Animal Director --------------

constructor TAnimalDirector.Create(const aBuilder: TAnimalBuilder);
begin
  FBuilder := aBuilder;
end;

function TAnimalDirector.BuildAnimal: TAnimal;
begin
  FBuilder.BuildBody;
  FBuilder.AddBehaviours;
  Result := FBuilder.GetAnimal;
end;

//--- Cat Builder --------------

procedure TCatBuilder.BuildBody; 
begin
  FCat := TCat.Create;
end;

procedure TCatBuilder.AddBehaviours; 
begin
  FCat.AddBehaviour(TSpeakBehaviour.Create('miauw'));
  FCat.AddBehaviour(TJumpBehaviour.Create(jaHigh));
end;

function TCatBuilder.GetAnimal: TAnimal; 
begin
  Result := FCat;
end;

//--- Dog Builder --------------

procedure TDogBuilder.BuildBody; 
begin
  FDog := TDog.Create;
end;

procedure TDogBuilder.AddBehaviours; 
begin
  FDog.AddBehaviour(TSpeakBehaviour.Create('woof'));
  FDog.AddBehaviour(TJumpBehaviour.Create(jaMediumHigh));
end;

function TDogBuilder.GetAnimal: TAnimal; 
begin
  Result := FDog;
end;

OTHER TIPS

It depends on why you are using the factory method pattern.

If the main purpose is to be able to return a subclass of Cat instead of a plain Cat, using multiple factory methods might suit the purpose better, with the additional advantage of removing the "magic strings" passed to create:

class AnimalFactory {
  public static function createCat($canJumpHigh) {
    // in the future, this can be extended to return a HouseCat instead
    return new Cat($canJumpHigh);
  }
  public static function createDog($canBark) {
    return new Dog($canBark);
  }
}

Why do you have public variables like blnCanBark and blnCanJumpHigh instead of proper set_property functions.

If I simple try to understand the above code then i think there should have been a function set_properties() with unlisted number of parameters or better set_properties(array $properties).

It may not be manageable with simple functions and I would have gone for abstract class and interface for more complex logic.

But if simplicity is desirable then set_properties(array $properties) is the solution.

Licensed under: CC-BY-SA with attribution
scroll top