首先,我们从鸭子说起,鸭子都会叫和游泳,但不同的鸭子有不同的外观:
先写一个父类:
public abstract class Duck {
public void quack(){
System.out.println("I'm duck, I can quack!");
}
public void swim(){
System.out.println("I'm duck, I can swim!");
}
public abstract void display();
}
绿头鸭:
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("I'm green head duck");
}
}
红头鸭:
public class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("I'm red head duck");
}
}
但是,有了新的需求,我们想让鸭子飞:在父类写个fly()方法,所有的子类都继承该方法,但是,有的鸭子不会飞的,例如橡胶鸭子。这显然不是我们想要的。或许你会说,我们可以在子类中覆盖该方法,让它什么都不做,但是,如果有越来越多的其它类型的鸭子需要添加进来那又该如何?显然,会有越来越多重复的代码在子类中出现,而且如果父类稍微有点改动都会涉及到子类。继承,显然不是我们推崇的。
那么你会说,用接口来抽离出鸭子飞行和叫法的行为又怎样?
是的,这样是解决了一部分问题,起码不会出现会飞的假鸭子,但是,如果很多鸭子是会飞行以及叫法一样的,是不是又造成代码无法复用的问题?接口不具备实现功能,所以无法达到代码的复用。
但是,问题总是得解决的。我们只能从涉及原则中寻找答案了:
把可能变化的代码抽离并封装起来,好让其它部分不受影响。这是一个很简单的概念,也几乎是每个设计模式背后的精神所在。
好的,是时候对duck进行改造了,记住上面的原则:把可能变化的代码抽离并封装起来,这里的quack()和fly()是会随着鸭子的不同而改变的。
先把fly和quack抽离出来:
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
接下来,需要飞行或不需要飞行的都实现FlyBehavior接口,叫或不叫以及叫法不同的实现QuackBehavior 接口:
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can fly");
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly");
}
}
类似地,quackBehavior同样如此实现。
如此一来,变化的部分已经和duck完全无关了,但是我们接下来要做的就是把他们联系起来,让不同的鸭子飞或不飞,叫或不叫:
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public void performQuack(){
quackBehavior.quack();
}
public void performFly(){
flyBehavior.fly();
}
public void swim(){
System.out.println("I'm duck, I can swim!");
}
public abstract void display();
}
这样,就把具体实现都留给了具体行为的实现类,现在,我们来看看子类的改造:
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("I'm green head duck");
}
}
最后来个测试类看看效果:
public class Main {
public static void main(String[] args) {
Duck greenDuck = new GreenHeadDuck();
greenDuck.disPlay();
greenDuck.performFly();
greenDuck.performQuack();
Duck redDuck = new RedHeadDuck();
redDuck.disPlay();
redDuck.performFly();
redDuck.performQuack();
}
}
在此,我们需要记住一个原则:针对接口编程,而不是针对实现编程。由始至终,我们的程序都是尽量地遵循这个原则的。
为了使这个应用更加地弹性,我们还可以在Duck中添加set方法,动态地改变鸭子的行为,万一它哪天会飞了呢?
public void setFlyBehavior(FlyBehavior fb){
this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb){
this.quackBehavior = qb;
}
Main方法里:
Duck redDuck = new RedHeadDuck();
redDuck.disPlay();
redDuck.performFly();
redDuck.performQuack();
redDuck.setFlyBehavior(new FlyWithSwings());
redDuck.performFly();
其实,这就是另外一个设计原则:少用继承,多用组合。如同本例,当你将两个以上的类结合使用,就是组合。鸭子的行为不是继承过来的,而是适当地与行为对象组合而来的。
软件开发总是要花大量的时间在维护与升级上,而好的设计模式往往能帮我们减少大量的时间的消费。多用组合,少用继承。
最后,也是时候揭晓谜底了,这就是策略模式:
所谓策略模式就是定义了算法簇,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。