策略模式
我的设计模式的学习,是通过《Head First设计模式》这本书来学习的。在学习的过程中,自己再总结一遍。如果在这儿有什么错误或者不同的见解,希望指出。
鸭子应用
先模拟一个应用,根据这个应用的实现方式来引出模式。
简单构思一个鸭子的应用。鸭子会叫,鸭子会游泳。不同的鸭子,长得不同。假设现在存在外观是黄色的黄头鸭和外观是红色的红头鸭。那么这个鸭子类可以定义为如下:
/**
* 定义抽象类鸭子。
**/
public abstract class Duck {
public abstract String display();
public void quack() {
System.out.println(display() + "呱呱叫...");
}
public void swim() {
System.out.println(display() + "游泳...");
}
}
/**
* 黄头鸭。
**/
public class YellowHeadDuck extends Duck {
@Override
public String display() {
return "黄头鸭";
}
}
/**
* 红头鸭。
**/
public class RedHeadDuck extends Duck {
@Override
public String display() {
return "红头鸭";
}
}
上面的这个方式,很简单,设计也没有问题。测试一下吧。
public class TestDuck {
public static void main(String[] args) {
Duck duck = new YellowHeadDuck();
duck.quack();
duck.swim();
}
}
输出结果:
黄头鸭呱呱叫...
黄头鸭游泳...
似乎还顺利。但是随着后面需求的增加,现在又多了一种鸭子:橡皮鸭。对于多出来的这个鸭子,大多开发工程师都会想,继续继承Duck类不就可以了嘛,但是,有问题。下面演示一下:
public class RubberDuck extends Duck {
@Override
public String display() {
return "橡皮鸭";
}
}
测试橡皮鸭,会输出”橡皮鸭呱呱叫”、”橡皮鸭游泳”。橡皮鸭呱呱叫是不符合逻辑的,因为橡皮鸭是模拟叫声,并非真正叫。这算是一个bug。要解决这个bug也很简单,就是让橡皮鸭来重写quack方法。
public void quack(){
System.out.println("模拟鸭子呱呱叫...");
}
这样就搞定了。但是,有没有发现什么问题呢?现在只加了一种鸭子,比如现在又增加一种模型鸭。它只是一个模型,不会叫。继续继承Duck,然后继续覆盖quack方法。那如果继续增加鸭子呢。不同的鸭子可能叫法不同。
这个问题,暂时放一下。再增加一个新的需求。让鸭子具有飞的行为。
方法1:将飞的行为添加在抽象鸭子类中,让每一个鸭子具有飞行行为。
这种方式是不对的,因为橡皮鸭和模型鸭是不会飞的,如果这样加,必须要让这两个鸭子去覆盖飞行方法。鸭子多了,可能会忘记哪个需要覆盖,哪个不需要覆盖。
方式2:在抽象鸭类中增加抽象方法,让具体鸭子来实现飞的行为。
这种方式是可以考虑的。增加一个抽象方法,就需要让每一个继承了它的子类去实现这个方法。好的,代码如下:
/**
* 抽象鸭子
**/
public abstract class Duck {
public abstract void fly();
//...其他
}
/**
* 黄头鸭
**/
public class YellowHeadDuck extends Duck {
@Override
public String display() {
return "黄头鸭";
}
@Override
public void fly() {
System.out.println("黄头鸭正常的飞...");
}
}
/**
* 红头鸭
**/
public class RedHeadDuck extends Duck {
@Override
public String display() {
return "红头鸭";
}
@Override
public void fly() {
System.out.println("红头鸭正常飞...");
}
}
/**
* 橡皮鸭
**/
public class RubberDuck extends Duck {
@Override
public String display() {
return "橡皮鸭";
}
@Override
public void fly() {
//不会飞,什么也不做。
}
public void quack(){
System.out.println("模拟鸭子呱呱叫...");
}
}
/**
* 模型鸭
**/
public class ModelDuck extends Duck {
@Override
public String display() {
return "模型鸭";
}
@Override
public void fly() {
//不会飞,什么也不做。
}
public void quack(){
//不会叫,什么也不做。
}
}
这样其实也有问题,橡皮鸭和模型鸭不具备飞的能力,为什么还要让他具有飞的方法呢?如果不把fly()定义在抽象类中,让每一个具有飞的行为的鸭子单独来增加fly方法,又会有两个问题会发生:
1. 2个鸭子要增加2个方法。如果不仅仅有黄头鸭,红头鸭,还有绿头鸭、褐头鸭等等各种鸭子20个,需要增加20个同样的方法。
2. 如何调用鸭子的fly方法。强制转换。举个例子:
public class BeforeFly {
public void before(Duck duck) {
System.out.println("在鸭子飞之前,做点事...");
duck.fly();
/**
* 你本想这样调用,但是很遗憾,Duck并没有这样的方法。你需要把Duck类转换为具体的类。
*/
/*
if (duck instanceof YellowHeadDuck) {
(YellowHeadDuck)duck.fly();
} else if (duck instanceof RedHeadDuck) {
(RedHeadDuck)duck.fly();
}
*/
//上面这样合适吗?肯定不合适。
}
}
上面都是一些反例,你肯定也感受到了。如果要增加一种行为,而这种行为,每种鸭子实现方式不同,就不能把这种行为定义在公共抽象类里。所以,策略模式诞生了。
使用模式
设计原则:
找出应用中可能要变化的内容,把它们独立出来,不要和那些不变的内容混在一起。
多用组合,少用继承
这个鸭子模型中,其他都是一样的,只有fly方法和quack方法有点不同。那么,我把这两个不同的行为独立出来,重新来设计这个鸭子应用。
独立飞的行为
/**
* 飞的行为接口
**/
public interface FlyAction {
void fly();
}
/**
* 正常飞
**/
public class NormalFlyAction implements FlyAction {
@Override
public void fly() {
System.out.println("正常的飞...");
}
}
/**
* 不会飞
**/
public class NotFlyAction implements FlyAction {
@Override
public void fly() {
System.out.println("不会飞...");
}
}
独立叫的行为
/**
* 叫的行为
*/
public interface QuackAction {
void quack();
}
/**
* 正常叫
*/
public class NormalQuack implements QuackAction {
@Override
public void quack() {
System.out.println("呱呱叫...");
}
}
/**
* 模仿叫
*/
public class ImitateQuackAction implements QuackAction {
@Override
public void quack() {
System.out.println("模仿呱呱叫...");
}
}
/**
* 不会叫
*/
public class NotQuackAction implements QuackAction {
@Override
public void quack() {
System.out.println("不会叫...");
}
}
重构鸭子类
public abstract class Duck {
QuackAction quackAction;
FlyAction flyAction;
public abstract String display();
public void swim(){
System.out.println(display() + "游泳...");
}
public void quack(){
quackAction.quack();
}
public void fly(){
flyAction.fly();
}
}
public class YellowHeadDuck extends Duck {
public YellowHeadDuck() {
this.quackAction = new NormalQuack();
this.flyAction = new NormalFlyAction();
}
@Override
public String display() {
return "黄头鸭";
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
this.quackAction = new ImitateQuackAction();
this.flyAction = new NotFlyAction();
}
@Override
public String display() {
return "橡皮鸭";
}
}
public class ModelDuck extends Duck {
public ModelDuck() {
this.quackAction = new NotQuackAction();
this.flyAction = new NotFlyAction();
}
@Override
public String display() {
return "模型鸭";
}
}
然后测试:
public class TestDuck {
public static void main(String[] args) {
Duck yellowHeadDuck = new YellowHeadDuck();
Duck rubberDuck = new RubberDuck();
Duck modelDuck = new ModelDuck();
yellowHeadDuck.fly();
yellowHeadDuck.quack();
rubberDuck.fly();
rubberDuck.quack();
modelDuck.fly();
modelDuck.quack();
}
}
输出:
黄头鸭正常的飞...
黄头鸭呱呱叫...
橡皮鸭不会飞...
橡皮鸭模仿呱呱叫...
模型鸭不会飞...
模型鸭不会叫...
使用策略模式,如果不管后面再增加什么样的鸭子,都不需要再去维护飞的行为和叫的行为。即使已经存在的鸭子要改变某个行为,也只增加一个新的行为类即可,而不需要再去修改代码。
上面的代码,其实存在一个小小的问题。就是在每一个鸭子模型中去new对应的行为,这样的做法让代码高耦合了。不建议这样使用。改变方式可以参考spring的bean实现原理。即提供get/set方法。