对于代码中总是会有需要变化的部分和需要固定不变的部分。对于需要变化的部分,一般可以采用继承的方式,让子类对父类的方法进行重写,以改变已有的行为;如果变化的部分并不是所有子类都必须要有的,那就可以采用接口的形式,含有该行为的子类实现该接口,不含该行为的子类就不用实现,这样就可以保证子类的按序扩展。
这种继承父类和实现接口的方式在大多数情况下是比较有效的,但是当情况比较复杂的时候,比如子类并不需要父类的所有方法,那么继承就造成代码在子类中重复而且子类中含有不合时宜的方法。如果使用接口,子类按需实现接口,如果子类继承接口,子类中需要覆写的代码有时就会很多且重复甚至是没必要但也不得不遵守java语法规则去一个一个的覆写这些方法,而且当需要修改接口的时候,就需要将所有实现该接口的类也进行修改。
在继承父类和实现接口都显得很无力的时候,就可以考虑采用策略模式将需要改变的部分单独独立出来,每一种需要改变的部分实现为一组类,不要将其与不变的代码混在一起。
策略模式定义及组成
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
策略模式的组成:
- 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
- 具体策略角色:包装了相关的算法和行为。
- 环境角色:持有一个策略类的引用,最终给客户端调用。
策略模式类图
上图定义了一个鸭子父类Duck,两个接口flyBehaviour和quackBehaviour。其中的Duck及其子类相当于环境角色,给客户端调用,显示鸭子的一系列行为;两个接口分别相当于是抽象策略角色;两个接口的子类实现相当于具体策略角色,实现了抽象策略角色的方法并包装了各自的独特的行为。
此外上述的类图也体现了一个编程原则针对接口编程而不是针对具体实现编程,这样Duck类的行为就不会被具体实现给固定,便于扩展。
策略模式实际上使用了多个类的组合来实现系统的弹性,不仅将算法封装成了类而且还实现了在运行时动态的改变类的行为。这也符合多用组合少用继承的面向对象设计原则。
适用场景
对于一个类中既含有变化部分又含有不变的部分,而且变化部分需要随时间变化而不断的扩展的时候可以考虑使用策略模式。对于简单的代码使用继承和接口也是可以很好的完成相应的效果,但是对于比较复杂且需要不断扩展的系统,策略模式就可以提现价值了。
比如在一个系统中,所有对系统的操作都要有日志记录,而且这个日志还需要有管理界面,这种情况下通常会把日志记录在数据库里面,方便后续的管理,但是在记录日志到数据库的时候,可能会发生错误,比如暂时连不上数据库了,那就先记录在文件里面,然后在合适的时候把文件中的记录再转录到数据库中。对于这样的功能的设计,就可以采用策略模式,把日志记录到数据库和日志记录到文件当作两种记录日志的策略,然后在运行期间根据需要进行动态的切换。
http://chjavach.iteye.com/blog/698743
java示例代码
环境角色,鸭子父类
flyBehavior属性和quackBehavior属性以及这两个属性对应的set方法保证了可以在运行的时候动态的修改角色的行为。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
public abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
继承于该父类的环境角色–不同的鸭子类
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
public void display() {
System.out.println("I'm a rubber duckie");
}
}
抽象策略角色–可变部分的接口
鸣叫的接口
public interface QuackBehavior {
public void quack();
}
飞行的接口
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 can't fly");
}
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}
测试代码
public class StrategyTest extends TestCase {
@Test
public void testDuck(){
Duck mallard = new MallardDuck();
mallard.display();
mallard.performFly();
mallard.performQuack();
System.out.println("--------------------------------------------------------");
Duck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.performFly();
rubberDuck.performQuack();
System.out.println("--------------------------------------------------------");
Duck model = new ModelDuck();
model.display();
model.performFly();
model.performQuack();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
System.out.println("--------------------------------------------------------");
}
}
测试结果
策略模式总结
优点:
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。