策略模式
这个是第一次写设计模式博客的第一个模式,所以内容也会相对多一些。这些内容不光是为了做一个笔记,也是希望能帮助一些正在学习设计模式的同行(虽然知道也没什么人会看,不过不用在意这些)
此篇及以后所写的设计模式都是基于Head First设计模式为蓝本,里面会涉及一些本书内容,但并不想将近全部内容原封不动地照搬上来(如喜欢本书请支持购买正版)
看完本书第一个设计模式,作者也算是用心,花了很大篇幅介绍了一些OO编程原则。这里也想记录一下,跟读者分享一下。无论是不是有点过时,对我们后面的学习都是有一定的帮助。
对于这个策略模式,如果了解Java 8新特性的人可能觉得这个模式在逐渐淡出舞台。是的,我们不得不承认没有什么东西是一层不变的,技术也是不断运动发展的。就好像Java 8之前interface不能写实体方法,Java 8之后可以写了一样(以后面试提到关于interface的问题要小心了,不要在一位地背宝典,一定要多方面地去了解。可能随着技术的日新月异,这些问题也不会再问了吧)
要点
- 良好的OO设计必须具备可复用,可扩充,可维护三个特性。(说白了就是如果你的项目只要一修改就要牵一发而动全身的话,那说明整体架构设计的不是很好)
- 模式不是代码,而是针对设计问题的通用解决方案。你可把它们应用到特定的应用中。
- 大多数的模式都允许系统局部改变独立于其他部分。
OO原则
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不要针对实现编程
说了那么多正式进入主题了。问题来了,究竟什么是策略模式?笔者认为,策略模式的精髓就是:用接口解耦而避免用继承重写。可能很多人对类或者封装的理解是: 对于一个实物的抽象。说通俗点就是一个类包含描述这个实体的状态字段以及行为方法。然而在策略模式里有点打破这种思维,策略模式是把变化的行为方法独立出来封装进接口里,通过接口的多态解耦合。如果只是一位地继承父类重写父类方法,会造成很多重复的代码片段并且难以维护。这里多提一点,对于什么时候使用继承这种问题,笔者认为没有准确的答案,不过根据个人经验来看,继承是一种高度统一的思想(类似于农民家庭世世代代都继承家里的土地播种,觉得手里有地就是福,这里不讨论那些发愤图强改变命运的个例),在一定范围内没有太大变化的情况下使用的。下面我就来具体看看这个策略模式。
这里也是借用一下本书的duck例子。
父类duck
public class Duck {
public void quack () {
// 方法实现
}
public void fly() {
// 方法实现
}
}
非常easy。如果我们按照以往惯例,对于那些实实在在的对象,我们可能会选择继承这个父类,例如
public class MallarDuck extends Duck {
@Override
public void quack() {
System.out.println("mallar duck quack");
}
@Override
public void fly() {
System.out.println("mallar duck fly");
}
}
如果一般鸭子都有“叫声”,都会"飞"的话,那一两个实例用继承问题也不大。但是我们要长远考虑,如果后期会有各种各样的鸭子要实现,每次都继承父类重写这些方法那不是太费劲了。而且有些鸭子比较特别,不一定会飞但会叫,不会叫但会飞等等。假如我们规定我们实现的鸭子都不能叫了,那不是所有的鸭子实现类都要进行修改。。。。。这一系列的问题都促使我们不能一味地选择继承。很明显,这里的quack和fly行为动作是根据具体业务情况不断变化的,也就是说我们要根据情况能够“动态”地改变他们。那我们应该怎么去解决呢。
这里我们要把这些变化的行为抽出来,让接口来“组合”(参考上面说的OO原则)
先定义两个行为接口
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
根据不同的需求,创建他们的实现行为类。这里就列举一个。
public class FlyNoWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("cannot fly");
}
}
当然笔者写了几个实现,这里就不一一贴出来了。
最后我们看一下鸭子是怎么“动态”改变行为的
一样先看父类
public class Duck {
protected QuackBehavior quackBehavior;
protected FlyBehavior flyBehavior;
public void performQuack() {
quackBehavior.quack();
}
public void performFly() {
flyBehavior.fly();
}
public void display() {
System.out.println("I'm a duck");
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
这里稍微丰富了一下,比之前的多了很多。我们看到这个duck父类多了两个行为接口,就是我们上面定义的。
public class MallarDuck extends Duck {
public MallarDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyNoWings();
}
public void display() {
System.out.println("I'm a real mallar duck");
}
}
实现类里面,我们在构造器里去实例化鸭子的行为。
最后我们启动程序看一下
public class Entrance {
public static void main(String[] args) {
Duck mallarDuck = new MallarDuck();
mallarDuck.display();
mallarDuck.performFly();
// dynamically change fly behavior
mallarDuck.setFlyBehavior(new FlyRocketPowered());
mallarDuck.performFly();
}
}
在之前的例子中,父类duck实现了quack和fly行为类的set方法,当我们需要改变鸭子行为时,只要调用鸭子行为set方法就可以改变。同时,每个鸭子实现类都有不同的实现内容,我们只需实现那些行为接口就好。例如,鸭子a,鸭子b和鸭子c需要a1 a2 b1 b2方法,那我们只需实现a1 a2 b1 b2方法类,然后把这些方法实现类传入进鸭子a b c里(或在构造器里初始化),哪天要修改了就动态地调用一下set方法,就不用每次实现一个鸭子都要去实现一遍a b方法。是不是觉得这样要比继承的可扩展性好很多。