个人博客地址:https://alexaccele.github.io/
策略模式
策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计原则
设计原则:
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。即不需要知道实现细节,只根据接口相关信息编程
- 多用组合,少用继承。
案例
首先根据面向对象的思想要设计一系列鸭子时,我们会先创建好一个Duck基类,然后在这个基类中添加每个鸭子都会有的行为,例如swim()/quack()/fly()等等行为,于是我们开始给每个子类重写对应的方法,此时若要再添加行为时,我们不仅需要在基类中添加,还要为每个子类添加重写,又或者说是某些子类并不具有基类的全部行为,这时我们可能又要再去修改基类。无论是哪种情况都增加了我们维护和更新功能的负担,策略模式则可以为我们提供一种解决方案,提高代码的重用性。
我们尝试着把一些变化的行为分离出来,例如quack()和fly()
首先是这两个对应方法的接口
分离行为
quack行为的接口:
public interface QuackBehavior {
void quack();
}
fly行为的接口:
public interface FlyBehavior {
void fly();
}
根据行为的不同进行实现
在完成了变化行为的分离之后,我们可以针对具体的不同行为提供不同的实现
例如,fly的不同行为可能有不能飞和用翅膀飞两种
用翅膀飞:
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("fly with wings");
}
}
不能飞:
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("can't fly");
}
}
以及quack行为
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("quack quack quack");
}
}
整合
我们来完善以上提到的所有类和接口等
首先是我们的Duck基类
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void swim() {
System.out.println("all duck can swim");
}
}
注意在Duck基类中,有为行为接口类型声明的两个引用变量。
具体子类RubberDuck表示橡胶鸭
public class RubberDuck extends Duck {
public RubberDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("a rubber duck");
}
}
具体子类MallardDuck表示野鸭
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("a mallard duck");
}
}
然后我们编写一个测试类Test
public class Test {
public static void main(String[] args) {
Duck duck = new RubberDuck();
duck.performFly();
duck.performQuack();
duck.display();
duck.swim();
System.out.println("------------");
Duck duck2 = new MallardDuck();
duck2.performFly();
duck2.performQuack();
duck2.display();
duck2.swim();
}
}
于是我们可以看到运行结果:
can't fly
quack quack quack
a rubber duck
all duck can swim
------------
fly with wings
quack quack quack
a mallard duck
all duck can swim
这里可以看出我们实现了不同行为的分离,并且在使用时都是使用的基类以及基类的方法,在使用时与具体的实现相分离了,由此也可以看到策略模式的魅力。
动态设定行为
我们还可以对现在的代码进行进一步的完善,我们可以为Duck基类添加不同行为的set()方法,以实现动态的行为设定
尽管橡皮鸭不会飞,但我们可以通过将它抛在空中,于是它也有了“fly”的能力,因此我们实现一个新的fly行为
public class FlyWithThrow implements FlyBehavior {
@Override
public void fly() {
System.out.println("fly with throw");
}
}
测试:
public class Test {
public static void main(String[] args) {
Duck duck = new RubberDuck();
duck.performFly();
duck.setFlyBehavior(new FlyWithThrow());
duck.performFly();
duck.performQuack();
duck.display();
duck.swim();
System.out.println("------------");
Duck duck2 = new MallardDuck();
duck2.performFly();
duck2.performQuack();
duck2.display();
duck2.swim();
}
}
结果:
can't fly
fly with throw
quack quack quack
a rubber duck
all duck can swim
------------
fly with wings
quack quack quack
a mallard duck
all duck can swim
可以看到原本不会飞的橡皮鸭通过我们新的行为实现,也能够通过特定的方式进行"fly"了。
总结
让我们回过头来再看看策略模式的定义
策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
再看看我们的案例,首先分离出的两种行为fly和quack就是两个算法族,然后其中各自的具体实现就是封装,同一个算法族中的算法可以相互替换,因为接口相同,替换之后也就有了不一样的行为,这样的策略模式使得我们的代码有了更好的重用性。
例如,当我们再需要添加新的鸭子时,可以根据它的不同行为选择不同的算法族,而不用去修改原本已经写好的基类和子类。