策略模式
什么是策略
一般是指:
- 可以实现目标的方案集合。
- 根据形势发展而制定的行动方针和斗争方法。
- 有斗争艺术,能注意方式方法。
“策略”就是为了实现某一个目标,首先预先根据可能出现的问题制定的若干对应的方案,并且,在实现目标的过程中,根据形势的发展和变化来制定出新的方案,或者根据形势的发展和变化来选择相应的方案,最终实现目标。
(引用百度百科)
什么是策略模式
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。
策略模式:
- 定义了一族算法(业务规则);
- 封装了每个算法;
- 这族的算法可互换代替(interchangeable)。
策略模式UML图
(引用维基百科)
类图
-
Client
发起算法调用
-
Context(客户端)
持有具体策略实现的引用,调用策略接口API,进行方法调用。
可以持有全部策略引用,根据不同的策略标识获取已初始化的策略类,进而执行对应的策略行为。也可以通过构造方法传入策略类。
-
Strategy(策略接口)
决定实现策略所必需的接口。
-
AddStrategy(具体策略实现者)
实现策略接口,具体策略有不同的实现方式。达到解耦的目的。
实现
注。以下实现参考 <Head First 设计模式>
1. 定义策略接口
/**
* 剥离出来的特定的飞行行为
*/
public interface FlyBehavior {
void fly();
}
/**
* 剥离出来的特定的呼叫行为
*/
public interface QuackBehavior {
void quack();
}
2. 定义策略实现类
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("定义会呱呱叫的鸭子行为!!!");
}
}
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("定义会飞的鸭子行为!!!");
}
}
3. 定义Duck超类
/**
* 鸭子超类
* 需求一: 需要会飞的鸭子。
* 会飞这个动作并不是所有鸭子都具备的行为。如果在超类上加上新的行为,将会使得某些子类具有这个不恰当的行为。
* 方式一: 定义新的接口。让需要的这种鸭子行为的类实现它。但这不是一个好的办法。每当新增一个鸭子的种类,都要去实现这个接口。×
* 如果每次新的需求一来,都会变化到某方面的代码,那么你可以确定,这部分的代码需要被抽取出来。
* 方式二: 为要要区分开 变化和不变化的部分。准备再组类。一是fly相关,一是quack相关
* 当我们创建特定对象的时候,指定特定的类型的飞行行为给它。我们需要在鸭子超类中包含设定行为了方法。就可以在运行时动态地改变子类的飞行行为
*/
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
/**
* 外观不同,定义抽象方法让子类实现
*/
public abstract void display();
/**
* 呱呱叫
*/
public void quack() {
System.out.println("所有鸭子都会: 呱呱叫!!!");
}
/**
* 游泳
*/
public void swim() {
System.out.println("所有鸭子都会: 游泳!!!");
}
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
}
4. 定义Duck实现类
/**
* 绿头鸭子
*/
public class MallardDuck extends Duck{
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.printf("mallard duck.");
}
}
/**
* 闲鱼鸭
*/
public class NothingDuck extends Duck {
@Override
public void display() {
System.out.println("Nothing duck");
}
}
5. Main
/**
* 不再把鸭子的行为说成「一组行为」,我们开始把行为想成是「一族算法」
* 鸭子的行为不是继承而来,而是和适当的行为对象组合而来。
* 设计原则: 多用组合,少用继承。
*/
public class T {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.performFly();
mallardDuck.performQuack();
System.out.println("--------Nothing Duck--------------");
NothingDuck nothingDuck = new NothingDuck();
nothingDuck.performFly();
}
}
总结
- 针对接口编程,而不是针对实现编程
- 无论何时你需要修改某个行为,你必须得往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误。换句话说,如果每次新的需求一来,都会变化到某方面的代码,那么你就可以确定,这部分的代码需要被抽出来,和其他闻风不动的代码有所区隔。
- 把会变化的部分取出并封装起来,以便以后可以轻易地扩充此部分,而不影响不需要变化的其他部分。
- 针对接口编程,关键就在多态。利用多态,程序可以针对超类编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类的行为上。
- 策略模式提供了对"开闭原则"完美支持,用户可以在不修改原有系统的基础上选择算法行为,也可以灵活的增加新的算法行为。
- 策略模式提供了可替换
继承关系
的办法。继承使得动态改变算法或行为变得不可能。但是使用策略模式可以实现解耦。 - 策略模式缺点是使用者必须知道所有策略类,并自行决定使用哪一个策略类。也就意味着使用者必须理解这些算法之间的区别,以便在恰当的时机选择恰当的算法类。