java设计模式:一、策略模式

策略模式

我的设计模式的学习,是通过《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方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值