引言
当我们完成一个复杂的业务中常常会面临一个问题:针对一个对象的某个行为,不同的情境下有不同的处理方式;
就比如今天我要去上班,那么我需要以哪种交通方式去上班呢?可以有下面几种选择:
- 步行
- 公交
- 地铁
- 自行车
- 开车
当然还会有更多的选择,这只是列举了几种;我上班时会在不同的情况下选择不同的交通工具,这就是不同处理方式;
如果在代码中体现,我们可以选择用 if…else 或者 switch 来对不同的情境进行判断,从而选择相应的交通工具;这样想当然很简单,但是写出来的代码会很复杂,而且后期进行维护是很难的;
那么就需要想一个办法将这个对象和这个行为分开,这个行为就是一个算法,这样对于修改维护只需要针对这个算法就可以了;
这里就需要用到设计模式中的策略模式;
策略模式定义:
策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户;
下面我将会用一个模拟鸭子的例子来介绍这个模式;
张三的问题
某公司有一个模拟鸭子的业务,这个业务中将会定义多种不同类型的鸭子,有的会飞,有的会叫,并且飞行方式和叫声可能也会有不同;现在这个业务交给了张三去做,张三以熟练的OO技术,设计了一个父类为Duck,然后让各种不同的鸭子去继承这个父类,便轻而易举的完成了这个项目;
类图:
可以看到,红头鸭和绿头鸭都继承了Dunk并重写了display方法,是不是很简单,我们看看代码:
Duck类:
// 鸭子类(抽象类)
public abstract class Duck {
// 抽象方法:显示什么鸭子
public abstract void display();
// 飞行的方法
public void fly() {
System.out.println("I'm flying!!!");
}
// 叫的方法
public void quack() {
System.out.println("嘎嘎嘎");
}
}
MallardDuck类
// 绿头鸭
public class MallardDuck extends Duck{
@Override
public void display() {
System.out.println("我是一只绿头鸭!!");
}
}
RedheadDuck类
// 红头鸭
public class RedheadDuck extends Duck{
@Override
public void display() {
System.out.println("我是一只红头鸭!!");
}
}
输出测试一下:
// 测试相应的功能
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck(); // 绿头鸭
mallardDuck.display();
mallardDuck.fly();
mallardDuck.quack();
System.out.println("-----------------");
Duck redheadDuck = new RedheadDuck(); // 红头鸭
redheadDuck.display();
redheadDuck.fly();
redheadDuck.quack();
System.out.println("-----------------");
}
}
结果如下:
我是一只绿头鸭!!
I'm flying!!!
嘎嘎嘎
-----------------
我是一只红头鸭!!
I'm flying!!!
嘎嘎嘎
-----------------
就在张三沾沾自喜的时候,这时产品经理提出了新的要求:要加一种新的鸭子:橡皮鸭;
张三一想:橡皮鸭不能飞,而且橡皮鸭的叫声是“吱吱吱”,那么只要创建一个橡皮鸭的类,依然继承Dunk,然后重写Dunk中的fly和quack方法不久可以了嘛;
于是他又加了一个类:
RubberDuck类
// 橡皮鸭不会飞,叫声是吱吱吱,所以需要重写所有方法
public class RubberDuck extends Duck{
@Override
public void display() {
System.out.println("我是一只橡皮鸭!!");
}
@Override
public void fly() {
System.out.println("I can't fly!!!");
}
@Override
public void quack() {
System.out.println("吱吱吱");
}
}
这时张三突然意识到,这里使用继承好像并不是特别完美,虽然实现了功能,但是RubberDuck类把父类中的所有方法都重写了一遍,这样的代码就出现了重复啊;
张三又想:
Duck类中的fly和quack好像并不适用于所有鸭子的情况,有的鸭子不会飞,有的不会叫,有的叫声不一样,有的飞行方式不一样。。。。这样看来,这个父类并不是完美的;如果产品经理让我给鸭子多加一个游泳的行为,那么一旦加到Duck类中后,所有种类的鸭子可能都会面临修改的可能,这样也太麻烦了吧!那该怎么办呢?
这里总结一下张三通过继承来提供Duck行为存在的问题:
- 代码在多个子类中重复
- 运行时的行为不易改变
- 很难知道不同的鸭子具体的行为
- 改变会牵一发动全身,造成其他种类鸭子不需要的改变;
该怎么做?
这时张三突然想到:可以使用一个Flyable和一个Quackable接口,只让会飞的鸭子实现Flyable,会叫的鸭子实现Quackable接口不就可以了嘛;
真的可以吗?
这样的话就会重复的代码会更多,如果需要修改飞行的行为,那么就需要对所有实现飞行接口的代码进行修改;一旦需要加入新的行为,如果用接口,那就需要对所有的鸭子进行一个判断并实现该行为;
因为在这里张三只声明了三种类型的鸭子,如果是五十种呢?一百种呢?难道都需要一一修改吗?
其实出现这些问题的本质就是因为鸭子Duck的行为会在子类里不断地改变,并且如果让所有的子类都有这些行为也是不现实的;
且使用接口不能实现代码,就无法达到代码的复用,一旦修改了某个行为,就需要找到所有实现该行为的类去修改,不仅工作量更大,而且可能会出现新的错误;
这时李四给张三提建议:
只需要找到项目中可能需要变换的地方,并把这个变化独立出来,不和那些不会变化的代码混在一起不就可以了吗?
李四的意思其实就是:把Dunk中会变化的部分取出来,单独封装起来,这样就可以轻易实现更该和扩充该部分,且不会影响不会变化的内容;
那么下面张三需要做的就是:将鸭子变化的行为从Duck中取出封装起来了;
问题解决
张三对Duck进行一个分析:既然要分离变化的行为,那么在这个类中也就只有fly和quack行为会改变了,所以只需要把这俩行为拿出来然后封装起来就可以了;
这时又有了一个新的问题:
张三希望代码更有弹性,因为开始的代码没有弹性才这样做的,如果能够动态的改变鸭子的行为,那样一旦有需求改变肯定会容易很多;
张三灵机一动,想到了一个设计原则:
面向接口编程,而不是针对实现编程;
那么就是用接口来抽象这个行为,具体行为的表现模式实现这个接口就可以了;
所以张三准备了一个QuackBehavior接口和一个FlyBehavior接口,然后将他们聚合到Duck类中,这样就可以灵活的修改代码了;
由于产品经理提出了新的需求:增加一个不会飞不会叫的模型鸭,并且给它加一个火箭助力器,让它可以飞;
张三想:正好我在重新设计代码,不如就拿这个来试试代码,看看能不能达到预期要求;
张三先设计了QuackBehavior接口和FlyBehavior接口的类图
那么Dunk该怎么设计呢?我们可以让Dunk关联于这两个接口,这样就可以让Dunk类使用对应的方法了;
类图:
张三这次留了个心眼,为了能够实现运行时代码的动态拓展,所以加入了set方法,这样就可以随时随地的设置不同鸭子的行为了;
接下来就是实现代码了;
FlyBehavior接口
// 鸭子飞的接口
public interface FlyBehavior {
public void fly();
}
实现FlyBehavior接口:
// 用翅膀飞
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("I'm flying!!!");
}
}
// 不能飞
public class FlyNoWay implements FlyBehavior{
@Override
public void fly() {
System.out.println("I can't flying");
}
}
// 火箭喷射器飞
public class FlyWithRocket implements FlyBehavior {
@Override
public void fly() {
System.out.println("Fly with a rocket!!");
}
}
QuackBehavior接口
// 鸭子叫的接口
public interface QuackBehavior {
public void quack();
}
实现QuackBehavior接口
// 鸭子嘎嘎叫
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("嘎嘎嘎");
}
}
// 橡皮鸭吱吱叫
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("吱吱吱");
}
}
// 不会叫
public class MuteQuack implements QuackBehavior{
@Override
public void quack() {
System.out.println("我不会叫");
}
}
下面就是Duck类和具体不同种类的鸭子了
Dunk类
// 鸭子抽象类
public abstract class Duck {
// Duck方法关联于FlyBehavior和QuackBehavior接口
// 这两个接口同样聚合在Dunk类中
private FlyBehavior flyBehavior; // 鸭子飞属性
private QuackBehavior quackBehavior; // 鸭子叫属性
// 抽象方法:显示什么鸭子
public abstract void display();
// 飞行的方法
public void performFly() {
flyBehavior.fly();
}
// 叫的方法
public void performQuack() {
quackBehavior.quack();
}
// 设置飞行的方法
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
// 设置叫的方法
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
// 红头鸭
public class RedheadDuck extends Duck{
public RedheadDuck() {
this.setFlyBehavior(new FlyWithWings());
this.setQuackBehavior(new Quack());
}
@Override
public void display() {
System.out.println("我是一只红头鸭!!");
}
}
// 绿头鸭
public class MallardDuck extends Duck {
// 默认构造方法,目的是给父类两个接口实例化对象
public MallardDuck() {
this.setFlyBehavior(new FlyWithWings()); // 用翅膀飞
this.setQuackBehavior(new Quack()); // 嘎嘎叫
}
@Override
public void display() {
System.out.println("我是一只绿头鸭!!");
}
}
// 橡皮鸭
public class RubberDuck extends Duck{
public RubberDuck() {
this.setFlyBehavior(new FlyNoWay()); // 不能飞
this.setQuackBehavior(new Squeak()); // 吱吱叫
}
@Override
public void display() {
System.out.println("我是一只橡皮鸭!!");
}
}
// 模型鸭
public class ModelDuck extends Duck{
public ModelDuck() {
this.setFlyBehavior(new FlyWithRocket()); // 火箭喷射器飞
this.setQuackBehavior(new MuteQuack()); // 不会叫
}
@Override
public void display() {
System.out.println("我是一只模型鸭!!");
}
}
终于实现了所有的功能,张三怀着忐忑写了一个测试代码:
// 测试系统
public class MiniDuckSimulator {
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck(); // 绿头鸭
mallardDuck.display();
mallardDuck.performFly();
mallardDuck.performQuack();
System.out.println("-----------------");
Duck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.performFly();
redheadDuck.performQuack();
System.out.println("-----------------");
Duck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.performFly();
rubberDuck.performQuack();
System.out.println("-----------------");
Duck modelDuck = new ModelDuck();
modelDuck.display();
modelDuck.performFly();
modelDuck.performQuack();
modelDuck.setFlyBehavior(new FlyNoWay()); // 动态改变对象行为
modelDuck.performFly();
}
}
输出结果:
我是一只绿头鸭!!
I'm flying!!!
嘎嘎嘎
-----------------
我是一只红头鸭!!
I'm flying!!!
嘎嘎嘎
-----------------
我是一只橡皮鸭!!
I can't flying
吱吱吱
-----------------
我是一只模型鸭!!
Fly with a rocket!!
我不会叫
I can't flying
第二版的代码完美的实现了所有的功能,并且代码的弹性和拓展性都很不错,张三想:升职加薪这不就稳稳地嘛;
李四这时说:这个代码就是用到了设计模式之一 ——策略模式,想要升职加薪,光会这一个设计模式可不行,后面的路还长着呢;
体会到了设计模式的好处,张三下定决心好好学习设计模式;
总结
上面张三的例子可以看出策略模式的好处
- 不需要许多 if …else或者switch 判断语句
- 代码可拓展性好
- 符合开闭原则,便于维护
同样策略模式需要注意:每添加一个策略就要增加一个类,当策略过多是会导致策略类膨胀;
其实这个例子中还用到了一个设计原则: 多用组合和聚合,少用泛化(继承)
这里总结一下文中提到的 三种设计原则:
- 封装变化的行为
- 面向接口编程,不针对实现编程
- 多用组合聚合,少用继承
当然这三个只是这里用到的,对于设计原则可不止这三种,后面会一 一介绍;
再重新看一下策略模式的定义:
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。
策略模式结构图:
其实策略模式在Java源码中也有体现,简单举个例子:Constructor就用到了策略模式,我们可以通过实现它来创造不同的排序规则,感兴趣可以看看源码体验一下;
当然一个例子不足以让你会用策略模式,想要真正的掌握还是需要大量的练习和实践,希望这篇文章能给你带来一些启发!
欢迎大家的点评!