版权声明:本文为博主原创文章,欢迎转载,转载注明出处即可~~ https://blog.csdn.net/WuchangI/article/details/81255681
情景引入:
哆啦A梦不像大雄那么偷懒,每天要做什么他都会认真去完成。比如,星期一他要清理百宝袋, 星期二他要帮妈妈做家务, 星期三他要去陪小咪散步……但是,因为他怕以后事情一多起来,就不知道哪一天应该做哪些事情了。所以他打算做一份备忘录,以便备忘某一天应该做的事。有想法就马上Carry,现在他便忙起做备忘录来。。。
一、简介
- 策略模式(Strategy Pattern)属于行为型模式。
- 它使得一个类的行为或算法可以在运行时被更改。
- 主要解决在有多种算法相似的情况下,使用if/else语句过于复杂和难以维护的问题。
二、具体内容
它定义了算法家族,分别将其封装起来,让它们之间可以互相替换。此模式让算法的具体实现的变化不会影响到使用算法的客户。
三、结构组成
在该模式中,我们创建表示各种策略的对象和一个行为随着其拥有的策略对象的改变而改变的Context对象,其中策略对象改变Context对象的执行算法。
Strategy(抽象策略类/角色)
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类来充当Context。ConcreteStrategy(具体策略类/角色)
实现了Strategy定义的接口,实现并封装了具体算法的实现。Context(策略上下文类/角色 或 环境类/角色)
该类内部维护一个对Strategy对象的引用,需要使用ConcreteStrategy提供的算法,负责动态设置运行时Strategy具体的实现算法,最终交给客户端调用。
四、UML类图
五、情景例子的实现代码
将上面的情境用代码实现一番:
1. 没有使用策略模式的情况
public class Doraemon
{
private String day;
public Doraemon(String day)
{
this.day = day;
}
public void doSomethingInThisDay()
{
if(day.equals("Mon"))
{
System.out.println("清理百宝袋");
}
else if(day.equals("Tues"))
{
System.out.println("帮妈妈做家务");
}
else if(day.equals("Wed"))
{
System.out.println("去陪小咪散步");
}
}
}
public class Client
{
public static void main(String[] args)
{
Doraemon doraemon = new Doraemon("Tues");
doraemon.doSomethingInThisDay();
}
}
输出结果:
帮妈妈做家务
2. 使用策略模式的情况
//策略接口类(普通的一天)
public interface IDay
{
void doSomething();
}
//策略A(星期一)
public class Monday implements IDay
{
@Override
public void doSomething()
{
System.out.println("清理百宝袋");
}
}
//策略B(星期二)
public class Tuesday implements IDay
{
@Override
public void doSomething()
{
System.out.println("帮妈妈做家务");
}
}
//策略C(星期三)
public class Wednesday implements IDay
{
@Override
public void doSomething()
{
System.out.println("去陪小咪散步");
}
}
//使用了某种策略的Context类(哆啦A梦本人)
public class Doraemon
{
private IDay day;
public Doraemon(IDay day)
{
this.day = day;
}
public void doSomethingInThisDay()
{
day.doSomething();
}
}
//客户端
public class Client
{
public static void main(String[] args)
{
Doraemon doraemon = new Doraemon(new Tuesday());
doraemon.doSomethingInThisDay();
}
}
输出结果:
帮妈妈做家务
3.如果在使用上面的策略模式的基础上,不同策略互相替换
public class Client
{
public static void main(String[] args)
{
Doraemon doraemon = null;
doraemon = new Doraemon(new Tuesday());
doraemon.doSomethingInThisDay();
doraemon = new Doraemon(new Monday());
doraemon.doSomethingInThisDay();
doraemon = new Doraemon(new Wednesday());
doraemon.doSomethingInThisDay();
}
}
输出结果:
帮妈妈做家务
清理百宝袋
去陪小咪散步
4.如果突然备忘录需要增加新的某一天的待办事项
(则不需修改if/else语句,直接新建一个实现接口Day的新的日期类即可)
public class Friday implements IDay
{
@Override
public void doSomething()
{
System.out.println("回去21世纪陪一下世雄");
}
}
public class Client
{
public static void main(String[] args)
{
Doraemon doraemon = null;
doraemon = new Doraemon(new Tuesday());
doraemon.doSomethingInThisDay();
doraemon = new Doraemon(new Friday());
doraemon.doSomethingInThisDay();
}
}
输出结果:
帮妈妈做家务
回去21世纪陪一下世雄
六、优点
- 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为,Strategy的等级结构中使用继承有助于析取出这些算法中的公共功能(将公共的代码转移到父类里,从而避免重复的代码)。
- 简化了单元测试。因为每个算法都有自己独立的类,可以通过自己的接口进行单独测试。这样每个算法可以保证其本身没有错误,修改其中任意一个时也不会影响到其他的算法。
- 各个算法(策略)可以自由切换。
- 避免使用多重条件判断语句。 多重转移语句不易维护,它把“采取哪一种算法或采取哪一种行为的逻辑”与“算法或行为本身实现的逻辑”混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
- 提高了程序的可拓展性、可维护性。
七、缺点
- 策略类会增多。 会造成很多的策略类,每个具体策略类都会产生一个新类。
- 所有策略类都需要对外暴露。客户端必须知道所有的策略类,并自行决定使用哪一个策略类。 这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
八、什么时候用
- 一个系统中有许多类,而区分它们的只是它们本身的行为,使用该模式便可动态地让一个对象在许多行为中选择一种行为去做。
- 需在不同的情况下使用不同的策略(算法),或者策略还可能在未来用其他方式实现。
- 需对客户端隐藏具体策略(算法)的实现。
- 客户端知道所有的算法或行为的情况下。
- 如果一个系统的策略多于四个,就需要考虑使用混合模式,以解决策略类膨胀的问题。
九、其他想说的
- 策略模式是一种定义一系列算法的方法。而这些算法完成的都是相同的一类工作,只是具体实现不同而已,客户端可以用相同的方式去调用所有这些算法,减少了各种算法实现类与使用算法的类之间的耦合。
- 策略模式就是用来封装算法的,但在实际应用中,可以用它来封装几乎任何类型的规则,只要在分析过程中得出需要在不同的时间应用不同的业务规则时,就可以考虑使用策略模式来处理。
- 实际上,在基本的策略模式中,选择所用的具体算法(实现)的职责由客户端来承担,并将所选择的策略转交给策略模式的Context对象,则本身并没有解除客户端需要进行选择判断的压力。此时,我们可以考虑将策略模式与简单工厂模式结合(实际就是将Context和工厂类合二为一),将选择所用的具体算法(实现)的职责由Context(此时作为工厂类)来承担,这样客户端就不必进行选择判断了,职责减轻了。但是在Context中还是需要进行选择判断(含有if、else、switch代码),如果需要增加一种新算法,又必须改动这些选择判断语句。无可否认,当需求改变时,我们必须改动原有代码。但是我们应该想办法降低这个改动代码的代价,我们可以考虑使用反射技术来达到这个目的。