策略模式

定义

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

使用场景

  • 许多相关的类它们仅在行为上有所不同, 策略模式为类指定行为
  • 你需要不同的算法变体。当这些变体被实现为算法的类层次结构时,可以使用策略
  • 算法应该使用客户不知道的数据。 使用策略模式可以避免暴露复杂的算法和数据结构
  • 一个类定义了许多行为,这些行为在操作中显示为多个条件语句。 可以将相关的条件分支移动到自己的策略类中从而避免多个条件语句

例子

假设有一个和鸭子相关的游戏,现在已经有了几种鸭子的模型,如白色的鸭子、黑色的鸭子。新的需要是给鸭子增加飞和叫的功能,普通的鸭子是可以飞和叫的,但是如果鸭子的模型中加入了木偶鸭(木工制作)、皮皮鸭(塑料玩具)这两种类型,前者不会叫不会飞,后者叫声和真实的鸭子不一样,也不会飞。在这种情况下,鸭子模型的颜色、大小等属性是可以一致表示的,但是它们的行为有所不同。
如果采用继承,那么需要对每个模型中的特定方法( 如 不会飞的模型中的fly方法,叫声不同的duck方法 )进行覆盖或者修改。后续加入新的行为(在父类中),还需要把子类的行为都确认一遍,防止出现木偶鸭飞起来的情形;而且运行时的行为不容易改变。
如果采用接口,会飞的鸭子实现flyable接口,会叫的实现duckable接口,那么如果要修改飞行行为,是不是需要把所有会飞的子类都修改一遍?
我们先给出采用策略模式的程序:

// 基类
public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck(){}

    public abstract void display();

    public void performFly(){
        flyBehavior.fly();
    }
        
    // 动态设置行为
   public  void setFlyBehavior(flyBehavior){
        this.flyBehavior = flyBehavior;
    }
    public void setQuackBehavior(quackBehavior){
        this.quackBehavior = quackBehavior;
    }
    public void performQuack(){
        quackBehavior.quack();
    }

    public void swim(){
        System.out.println("All ducks float!");
    }
}

// 正常的鸭子
public class NormalDuck extends Duck{
    public NormalDuck(){
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }
    public void display(){
        System.out.println("I am a normal duck");
    }
}

// 木偶鸭
public class WoodDuck extends Duck{
    public WoodDuck(){
        quackBehavior = new MuteQuack();
        flyBehavior = new FlyNoWay();
    }
    public void display(){
        System.out.println("I am a wood duck");
    }
}

// 飞行行为
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'm cannot fly!");
    }
}

// 叫声行为
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("silecnce");
    }
}

// 仿真
public class DuckSimulator{
    public static void main(String[] args){
        Duck normal = new NormalDuck();
        normal.performQuack();
        normal.performFly();
    }
}

分析

首先, 把变化的部分和不会变化的部分分离开。上述例子中,鸭子的行为是变化的(不同模型行为不同),其它属性是不变的。把会变化的部分取出并封装起来,好让其它部分不会受到影响
其次,针对接口编程,而不是针对实现编程。鸭子的行为被放在分开的类中,此类专门提供行为的接口;这样鸭子就不需知道行为的实现细节。
针对接口编程关键在于多态。利用多态程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。举个针对接口编程的例子:一个抽象类Animal,两个具体实现类Dog和Cat继承Animal。做法如下:

public class Animal{
    public abstract void makeSound(){}
}
public class Dog extends Animal{
    public void makeSound(){
        bark();
    }
    public void bark(){
        ...
    }
}

// 针对实现编程
Dog d = new Dog();
d.bark();

// 针对接口/超类型编程
Animal animal = new Dog();
animal.makeSound();

最后,多用组合,少用继承。has-a可能比is-a好,可以多尝试组合。鸭子的行为不是继承来的,而是和适当的行为对象组合来的。

参考

iluwatar/java-design-patterns

你可能感兴趣的:(策略模式)