# 策略模式

# 现实案例:

  • 想要用 OO 技术,实现一堆鸭子对象;
  • 鸭子有 fly(飞) 和 quack(叫) 两种行为;
  • 每个鸭子的行为可能都各不相同;
  • 也并非所有的鸭子都会飞,都会叫(例如:木头鸭子);

# 方案一:❌ 用继承来实现:

  • 实现一个父类 Duck,里面定义好 flyquack 方法;
  • 再让各个鸭子子类去继承 Duck 父类的方法;
  • 根据鸭子行为的不同,再在子类里面去覆盖 flyquack 方法;
// 父类
public class Duck {
  public void quack() {
      System.out.println("呱呱叫");
  }

  public void fly() {
      System.out.println("飞");
  }
}

// 子类
public class DisabledDuck extends Duck {
  // 根据子类的要求,覆盖父类的方法
  @Override
  public void quack() {
      System.out.println("不会叫");
  }

  @Override
  public void fly() {
      System.out.println("不会飞");
  }
}

# 方案一的问题:

  • 在父类里定义的 flyquack 方法并不是每个子类都具备的。而且各个子类之间还有行为差异;
  • 通过继承,会让一些子类具有不适合他们的方法,还需后期重写覆盖,增加了代码量;
  • 如果有子类之间有相同的行为,但又和从父类继承来的行为不一致。就只能在多个子类各自重写相同的方法。这样使相同的代码在多个子类中重复;
  • 并没有提供一个方法,来让子类的行为可以在运行时修改。(实例化完子类,但是又想更改实例的行为);
  • 很难知道所有鸭子的全部行为。只能一个一个子类地去看;
  • 如果以后父类有新的特性,还需要一个一个子类的去检查,是否新特性适合子类。不适合的话,还要在子类进行变动;
  • 利用继承,父类和子类的耦合度太高;

# 方案二:❌ 利用接口来实现:

  • 我们可以把 flyquack 方法从父类中提取出来;
  • 各自放进一个 FlyableQuackable 接口;
  • 然后在子类中按需求各自实现这些接口;

2020-2-14-18-1-7.png

public class Duck {
}

public interface Flyable {
    public void fly();
}

public interface Quackable {
    public void quack();
}
public class MallardDuck extends Duck implements Flyable, Quackable {
    @Override
    public void fly() {
        //实现该方法
    }

    @Override
    public void quack() {
        //实现该方法
    }
}

# 方案二的问题:

  • 虽然通过接口,不会让子类去继承不需要的特性;
  • 但是,因为接口要在子类中去实现。那么每个子类都需要单独写一份 flyquack 方法;
  • 即使有的鸭子之间的行为是相同的,它们的代码也不能复用;
  • 最差的情况是,每个子类之间的行为都是相同的。那么如果这个行为的代码需要修改,就要到每一个子类里面都修改一遍;

# 方案三:✅ 采用策略模式,针对接口编程:

  • 在这个问题中,flyquack 行为是根据鸭子子类的不同而 “变化” 的。而鸭子其他的东西目前是 “不变的”。一个设计原则是 “把变化的部分从不变的部分抽离出来”;
  • 我们分别给 flyquack 行为各创建一组类。每一组类将实现各自的动作;
  • 先创建 FlyBehaviorQuackBehavior 接口/父类;
  • 之后再写一堆子类去分别实现这两个接口/父类;
  • 2020-2-14-21-52-55.png
  • 在 Duck 父类里,加上两个实例变量 flyBehaviorquackBehavior,它们的类型分别是上面 👆 定义的接口/父类;
  • 在鸭子子类实例化的时候,我们再将具体的行为实例,赋值给上面的两个实例变量;
  • 这样一来每个鸭子子类实例,都有了各自的行为了;
  • 我们甚至可以给两个实例变量设置 setter 函数,以让开发者在运行时,动态的修改行为类;
// 定义两个行为的接口
public interface FlyBehavior {
  public void fly();
}

public interface QuackBehavior {
  public void quack();
}
// 为两个接口,各创建一组实现类
public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        //什么都不做,不会飞
        System.out.println("不会飞");
    }
}

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        //实现了所有有翅膀的鸭子飞行行为。
        System.out.println("可以飞");
    }
}


public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        //实现呱呱叫的鸭子
        System.out.println("呱呱叫");
    }
}

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        //实现吱吱叫的鸭子
        System.out.println("吱吱叫");
    }
}
// Duck 父类
public class Duck {
  // 分别对应两个行为类的实例
  FlyBehavior flyBehavior;
  QuackBehavior quackBehavior;

  // setter, 用以改变行为类
  public void setFlyBehavior(FlyBehavior flyBehavior) {
      this.flyBehavior = flyBehavior;
  }

  public void setQuackBehavior(QuackBehavior quackBehavior) {
      this.quackBehavior = quackBehavior;
  }

  // 调用行为实例的方法
  public void performFly() {
      flyBehavior.fly();
  }

  public void performQuack() {
      quackBehavior.quack();
  }
}
// 鸭子子类
public class MallardDuck extends Duck {
    // 在创建实例的时候,分别给两个行为属性指定具体的行为类;
    public MallardDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }
}
public class Test {
    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        duck.performFly(); // 可以飞
        duck.setFlyBehavior(new FlyNoWay());
        duck.performFly(); // 不会飞
        duck.performQuack(); // 呱呱叫
    }
}

# 总结:策略模式

  • 策略模式定义了算法族,各个算法之间可以互相替换。此模式让算法的变化,独立于使用算法的客户
    • 在 👆 上面的例子中,我们各写了一组 fly 行为和 quack 行为的实现类,这两组行为就被称为 “算法族”。客户就是鸭子类,它里面会使用到具体的一个行为类;
  • 之所以这样写,是因为鸭子的行为是会变化的。所以设计时,应该 “把会变化的部分抽取出来进行封装,以便以后可以轻易的改动和扩展,而不影响不会变化的部分”;
  • 鸭子具体使用哪个行为,不在定义类的时候写,类里面只是定义了两个类型为算法族接口/父类的实例属性。具体的行为类型是在实例化的时候再决定。在设计时,应该 “针对超类型编程,而不是针对实现编程”;
  • 这种将多个类结合在一起使用,被叫做 “组合”。在设计时,应该 “多用组合,少用继承”;
上次更新: 8/22/2020, 8:14:09 AM