8

I have been interested in design patterns for a while and started reading "Head First Design Patterns". I started with the first pattern called the 'Strategy' pattern. I went through the problem outlined in the images below and first tried to propose a solution myself so I could really grasp the importance of the pattern.

So my question is that why is my solution to the problem below not good enough. What are the good / bad points of my solution vs the pattern? What makes the pattern clearly the only viable solution?

MY SOLUTION

Parent Class: DUCK

<?php
class Duck
{
 public  $swimmable;
 public  $quackable;
 public  $flyable;

 function display()
 {
  echo "A Duck Looks Like This<BR/>";
 }

 function  quack()
 {
  if($this->quackable==1)
  {
   echo("Quack<BR/>");
  }
 }

 function swim()
 {
  if($this->swimmable==1)
  {
   echo("Swim<BR/>");
  }
 }

 function  fly()
 {
  if($this->flyable==1)
  {
   echo("Fly<BR/>");
  }
 }


}
?>

INHERITING CLASS: MallardDuck

<?php
class MallardDuck extends Duck
{
 function MallardDuck()
 {
  $this->quackable = 1;
  $this->swimmable = 1;
 }

 function display()
 {
  echo "A Mallard Duck Looks Like This<BR/>";
 }
}
?>

INHERITING CLASS: WoddenDecoyDuck

<?php
class WoddenDecoyDuck extends Duck
{
 function woddendecoyduck()
 {
  $this->quackable = 0;
  $this->swimmable = 0;
 }

 function display()
 {
  echo "A Wooden Decoy Duck Looks Like This<BR/>";
 }
}
12
  • your solution is not exactly what is meant (or even a solution to the one) on the book
    – mauris
    Jan 30, 2011 at 8:32
  • can you help explain better Jan 30, 2011 at 8:35
  • 5
    I'm not sure that you can scan pages from Head First Design patterns and simply put them on the web. Apr 29, 2011 at 7:50
  • 1
    This looks like a Code Review. Apr 29, 2011 at 10:20
  • 2
    @Imran As is mentioned at Amazon, those pages are copyrighted material. They can't be freely posted here. Please edit the question to state the problem in your own words.
    – Adam Lear
    Apr 29, 2011 at 14:57

6 Answers 6

6

Your code will break when, e.g. ducks quack with a different sounds. The boolean will only lead to other booleans into really hairy if-statements.

The strategy pattern is simple though. Take in this instance the quack method and place it into it's own class or interface. An interface in php would be a class that does not contain any implementation other than methods that are stubs (i.e. nothing happens when you call them).

class Quackable {
    function quack() {}
}

That way you can create several quackable implementations:

class DuckQuack extends Quackable {
    function quack() {
        return "Quack!";
    }
}

class SilentQuack extends Quackable {
    function quack() {
        return ""; 
            // The decoy duck can't quack. It is silent.
    }
}

And because we did it strategy pattern wise, we can add more types of "quackery":

class DoubleQuack extends Quackable {
    function quack() {
        return "Quackety-quack!"
    }
}

The same can be applied to fly and swim methods for the Duck class. The way you implement a Duck class with a quackable would be like this (the quackable interface is supplied through the constructor, which is sort of how dependency injection works):

class Duck {
    protected $quackable;

    // The constructor
    function __construct($quackable) {
        $this->quackable = quackable;
    }

    function quack() {
        echo $this->quackable->quack();
    }
}

Implementing the mallard duck and decoy duck will be simple since you only need to supply what kind of quacks the duck should do:

class MallardDuck extends Duck {
    function __construct() {
        parent::__construct(new DuckQuack());
           // we construct the mallard duck with a duck quack
    }
}

class DecoyDuck extends Duck {
    function __construct() {
        parent::__construct(new SilentQuack());
           // we construct the decoy duck with a silent quack
    }
}

Using it all is simple:

$duck1 = new MallardDuck();
$duck2 = new DecoyDuck();

$duck1.quack(); // echoes out "Quack!"
$duck2.quack(); // echoes out "" (because decoy ducks don't quack)

I hope this all makes sense to you.

3
  • The code may have errors, it's been a while since I coded something in php. :-P
    – Spoike
    Jan 30, 2011 at 12:44
  • Besides, the OP needs to flip a few more pages to get to the Strategy pattern and the "composition before inheritance" principle in that book. :) The scanned pages prefaces with the problem of inheritance.
    – Spoike
    Jan 30, 2011 at 18:34
  • thanx for the explanation Apr 29, 2011 at 10:24
4

First of all, your problem has nothing to do with the strategy pattern. The idea of the strategy pattern is to factor out the responsibility for a certain behaviour into a different class. That way, you can plug in different behaviours into one instance at run time.

Now your solution works reasonably well for the scenario you're in, but the scenario is only an excercise, that is quite far from real world problems.
If you use this technique to tackle big projects, you risk winding up with 5-10 layers of inheritance, where each subclass is coupled to more and more flags. Such code is extremely fragile and verbous. Fiddling around with the internal state of all super classes is not exactly the OOP way of handling such things, because it blurs seperation of concerns.

A solution using interfaces is signifficantly cleaner, more robust and will thus prove more maintainable over time. Attempt not to make a general all-purpose base-duck, but rather make clear abstractions of the different aspects of different ducks. I just recently made a blog post on this subject, you might find helpful.

4
  • 1
    +1 but I must say, that this is the example given for the Strategy Pattern in the book, which is an excellent book. Unfortunately he has not shown the next pages where the fly, quack etc behaviours are designed and then plugged into the ducks (i.e. the part that applies the strategy pattern to the problem), but it is in the book.
    – Alb
    Jan 30, 2011 at 13:51
  • I have added the next page of the book to show why the interface solution is a total no no! Apr 29, 2011 at 7:37
  • @Alb : yes its about 13 pages Apr 29, 2011 at 7:37
  • @Imran: Sorry, but the author of the book is just plainly wrong. Interfaces do not say anything about implementation. The class may be implemented through composition or delegation (the strategy pattern being a special case). However, giving a fly-method to a duck that can't fly, just because you can't solve it otherwise is poor design. And it doesn't scale. What if you you also have RuberBall? Will the RuberDuck also have an empty roll-method?
    – back2dos
    Apr 29, 2011 at 8:15
3

Ah, good question. I don't think it is very good practice to have member variables that toggle functionality. Mainly the problem with this solution is that since you expose the fly and quack methods on the Duck class it would appear that you're saying that "All ducks can fly/quack." Whats worse is that, depending upon the runtime type of the duck instance i have, fly or quack may or may not do anything. This can lead to very confusing code.

Where as, in the example the book gives, when you are expecting a duck that can fly, you could type (or test, since you're using a dynamic language) if it was a Flyable duck.

If every time I had a duck, before I call quack on it I have to decide have to check whether or not the flyable member variable is set to true that would lead to a lot of code duplication.

if($duck->quackable) $duck->quack();

The Flyable interface helps establish expectations. Yes, your default quack method will check whether it should or not, but I, as the caller, have no guarantees that it's even safe to call quack. Also think about the scenario with the RubberDucky which should squeak rather than quack. When you override the quack method you have to know to check the $quackable member variable before you take any action.

Something else that I think makes this solution unclear is that since you're using a dynamic language like PHP, it makes setting expectations about input types difficult. (Not that there is anything wrong with this, but a staticly typed language might make it easier to understand.)

I hope this helps and makes sense.

0
1

Your code will get messy quickly in the case that you have say 10 different types of ducks that all have different combinations of say 3 different variants of fly, quack and swim.

Personally, I found that the true value of the strategy pattern became clear when I started writing unit tests and using dependency injection. If you're not using it you'll have a lot of trouble managing dependencies and thus creating objects in your tests.

You'll get to situations where to create a duck become complicated, then you might want to write a unit test for some of the quacking or flying methods, but you can't do so without creating a duck in your test.

1

Having a boolean state to store whether ducks can quack / fly, is a very specific solution to the particular problem you're facing in your class design and may not be applicable to other cases where the strategy pattern is approprirate.

I think your 'quackable' approach makes the code a bit more convoluted. It's one more thing you, and users of your class have to be aware of than would be necessary if you were using a strategy pattern.

Finally, as your sample code doesn't show any examples of how you would override the 'quack' and 'fly' methods, youre not noticing a huge benefit of using the strategy pattern - cutting down on code duplication. What would you do given a dozen different duck classes which amongst themselves have 3 different 'fly' behaviors? Using your approach, you'd probably find yourself cutting and pasting, and then possibly extracting the duplicated code into static 'helper' classes. The strategy pattern is much cleaner.

1

A nice visual demo is always worth a thousand words.

John Lindquist is doing a great job to record screencasts about different Design patterns. You can find his 50 cents about this particular pattern (and much more) on his blog

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.