From Head First Design Patterns.
Design Principle:
Idnetify the aspects of your application that vary and separate them from what stays the same.
Here‘s another way to think about it:
Take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don‘t.
SimUDuck App
We need to separate behaviors from Duck class. So, let‘s define two behavior interfaces: QuackBehavior and FlyBehavior with additonal instantial classes.
Design Principle
Program to an interface, not an implementation.
QuackBehavior interface and instantial classes:
public interface QuackBehavior { public void quack(); } public class Quack implements QuackBehavior { public void quack() { System.out.println("Quack"); } } public class MuteQuack implements QuackBehavior { public void quack(){ System.out.println("<< Silence >>"); } } public class Squeak implements QuackBehavior { public void quack() { System.out.println("Squeak"); } }
FlyBehavior interface and instantial classes:
public interface FlyBehavior { public void fly(); } public class FlyWithWings implements FlyBehavior { public void fly() { System.out.println("I‘m flying!!"); } } public class FlyNoWay implements FlyBehavior { public void fly() { System.out.println("I can‘t fly"); } }
And then, we can define the abstract class Duck, which is the base class of our Duck Project:
public abstract class Duck { QuackBehavior quackBehavior; FlyBehavior flyBehavior; public Duck() { } public abstract void display(); public void performQuack() { quackBehavior.quack(); } public void performFly() { flyBehavior.fly(); } public void swim() { System.out.println("All ducks float, even decoys!"); } }
Now, we can make a concrete duck, Mallard!
public class MallardDuck extends Duck { public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); } public void display() { System.out.println("I‘m a real Mallard duck"); } }
Finally,let‘s write a simulator to test our codes:
public class MiniDuckSimulator { public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.performQuack(); mallard.performFly(); mallard.display(); } } /* Output: Quack I‘m flying!! I‘m a real Mallard duck */
Pay attention, the duck mallard here only offer two APIs: performQuack(), performFly(). We can‘t call quack() or fly(), which also make the process of calling behaviors simple.
It‘s a shame to have all dynamic talent built into our ducks and not be using it!
If you want to set duck‘s behavior now, you should make it through instantiating it in the duck‘s constructor rather than adding a setter method on the duck subclass.
Add two new methods to the Duck class:
public void setFlyBehavior(FlyBehavior fb) { flyBehavior = fb; } public void setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; }
Let‘s make a new Duck type:
public class ModelDuck extends Duck { public ModelDuck() { quackBehavior = new Quack(); flyBehavior = new FlyNoWay(); } public void display() { System.out.println("I‘m a model duck"); } }
adding new FlyBehavior type:
public class FlyRocketPowered implements FlyBehavior { public void fly() { System.out.println("I‘m flying with a rocket!"); } }
Now, we can add some codes in simulator to test our set methods:
Duck model = new ModelDuck(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly();
Here, the model duck dynamically changed its flying behavior!
You can‘t do THAT if the implementation lives inside the duck class!
Design Principle
Favor composition over inheritance.
In this project, we use the Stragegy pattern, the formal definition is:
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.