文章更新时间:2020-11-4 15:53:42
当我们实现某个接口时,可能会有很多种不同的实现方式。这些不同的实现方式通过一定的规则可以随意切换使用时,我们就可以考虑使用策略模式来实现。例如本文将要做的事情:打印TES与DWG进行BO5的所有结果。
一、定义
Define a family of algorithms,encapsulate each one, and make them interchangeable.
定义一组算法,将每个算法都封装起来,并且使他们之间可以转换。
二、UML类图
图片来自于《大话设计模式》
1:Context—上下文角色
Context也叫封装角色。在Context中封装了对具体策略的调用,将高层模块与具体策略角色族解耦,高层模块只需要调用Context类即可。
2:Strategy—抽象策略角色
抽象的策略接口,定义了每个策略都必须具有的方法和属性。
3:ConcreateStrategy—具体策略角色
实现了策略接口的具体策略类,并且实现了具体需要执行的策略。
三、代码示例
定义一个抽象策略接口,有一个方法:TES与DWG比赛。
public interface Strategy{
void TES_VS_DWG();
}
接下来实现Strategy接口,定义两个具体的策略类。
public class ConcreateStrategyA implements Strategy{
@Override
public void TES_VS_DWG(){
System.out.println("TES:DWG - 3:0");
}
}
public class ConcreateStrategyB implements Strategy{
@Override
public void TES_VS_DWG(){
System.out.println("TES:DWG - 3:1");
}
}
策略模式的重点是上下文角色类,他封装了对具体策略的调用。上下文角色类中维护着一个抽象角色的引用,高层模块在调用上下文角色类时,通过构造方法或其他方式将具体的策略角色实例赋给该引用,然后调用上下文角色类中的方。通过高层模块在上下文角色类中设置的不同具体策略实例即可执行不同的具体策略。
public class Context{
//抽象角色
private Strategy strategy = null;
//通过构造方法设置具体的策略,也可用其他方式如:set方法
public Context(Strategy strategy){
this.strategy = strategy;
}
//调用策略方法
public void printTheS10Result(){
strategy.TES_VS_DWG();
}
}
我们所说的高层模块,其实就是调用Context的类而已。在这个类中我们将具体策略类的实例通过Context的构造方法赋值给抽象策略角色的引用,也即是子类的实例赋值给父类的引用,我们就可以通过父类的引用调用的具体子类实现的方法。
public class Client{
public static void main(String[] args) {
//执行具体抽象类A
ConcreateStrategyA concreateStrategyA = new ConcreateStrategyA();
Context contextA = new Context(concreateStrategyA);
contextA.printTheS10Result();
//执行具体抽象类B
ConcreateStrategyB concreateStrategyB = new ConcreateStrategyB();
Context contextB = new Context(concreateStrategyB);
contextB.printTheS10Result();
}
}
执行main方法结果如下:
TES:DWG - 3:0
TES:DWG - 3:1
1:策略扩展
显而易见BO5的结果明显不止上面两种,还有其他情况,所以当我们想打印出其他结果时,就出现了策略扩展的情况。例如现在要增加一个具体策略类(ConcreateStrategyC),他的执行结果为:TES:DWG - 3:2
,则只需要完成下面两个步骤即可。
第一步实现抽象角色Strategy
public class ConcreateStrategyC implements Strategy{
@Override
public void TES_VS_DWG(){
System.out.println("TES:DWG - 3:2");
}
}
第二步高层模块调用
public class Client{
public static void main(String[] args) {
//执行具体抽象类A
ConcreateStrategyA concreateStrategyA = new ConcreateStrategyA();
Context contextA = new Context(concreateStrategyA);
contextA.printTheS10Result();
//执行具体抽象类B
ConcreateStrategyB concreateStrategyB = new ConcreateStrategyB();
Context contextB = new Context(concreateStrategyB);
contextB.printTheS10Result();
//执行新增加的具体抽象类C
ConcreateStrategyC concreateStrategyC = new ConcreateStrategyC();
Context contextC = new Context(concreateStrategyC);
contextC.printTheS10Result();
}
}
通过以上两个步骤就新增了一个具体策略类,同样通过这两步骤也可以很方便的新增其他具体策略类,打印出其他的结果。这就非常符合开闭原则:对扩展开放,对修改关闭。(呵呵,顺便提一下本以为是S10决赛是TES对DWG,每想到输给了苏宁。苏宁决赛虽败犹荣,如果滔博决赛输了还不得被喷成渣,他们也算是塞翁失马了)。
2:简单改进版
上面是一套比较简单的策略模式的代码实现,但是上下文角色类我比较推荐通过setter方式实现对抽象角色引用的赋值,并且整体采用build模式实现。通过这种方式可以不用每次都创建一个上下文角色对象,而且当抽象角色接口中方法比较多时,我们在高层模块调用也会比较舒服。
抽象接口以及具体策略类ABC与上面相同,上下文角色类代码如下:
public class Context1 {
//抽象角色
private Strategy strategy = null;
//通过set方法给Strategy赋值,并返回对象本身
public Context1 setStrategy(Strategy strategy) {
this.strategy = strategy;
return this;
}
//调用策略方法
public Context1 printTheS10Result() {
strategy.TES_VS_DWG();
return this;
}
}
高层模块类如下所示:
public class Client1 {
public static void main(String[] args) {
//实例化具体抽象类A、B
ConcreateStrategyA concreateStrategyA = new ConcreateStrategyA();
ConcreateStrategyB concreateStrategyB = new ConcreateStrategyB();
ConcreateStrategyC concreateStrategyC = new ConcreateStrategyC();
//实例化一个上下文角色类
Context1 context1 = new Context1();
//调用上下文角色类
context1.setStrategy(concreateStrategyA).printTheS10Result();
context1.setStrategy(concreateStrategyB).printTheS10Result();
context1.setStrategy(concreateStrategyC).printTheS10Result();
}
}
四、策略模式优化–结合工厂模式
策略模式优点:
- 算法自由切换:高层模块传入哪个具体抽象类的实例就执行哪个
- 具有良好的扩展性:扩展满足开闭原则
- 避免使用多重条件判断
策略模式缺点:
- 所有具体策略角色都需要对高层模块暴露,即需要在高层模块中实例化具体策略角色,然后赋值给上下文角色类中的抽象角色引用
- 可能出现策略膨胀问题:例如比赛不是BO5,而是BO10000,那要打印所有结果的话,就会有非常多的具体策略类,这就是策略膨胀。
针对这些策略模式的缺点,我们可以通过将策略模式与简单工厂模式结合使用来解决这些问题。整合之后的代码与上面的通用代码只有上下文角色类与高层模块类不同。具体如下所示:
public class Context2 {
//抽象角色
private Strategy strategy = null;
//将实例化具体策略的过程从高层模块转移到Context类中。简单工厂的应用
public Context2(String strategyType) {
switch (strategyType) {
case "strategyA":
this.strategy = new ConcreateStrategyA();
break;
case "strategyB":
this.strategy = new ConcreateStrategyB();
break;
case "strategyC":
this.strategy = new ConcreateStrategyC();
break;
}
}
//调用策略方法
public void printTheS10Result() {
strategy.TES_VS_DWG();
}
}
public class Client2 {
public static void main(String[] args) {
Context2 strategyA = new Context2("strategyA");
strategyA.printTheS10Result();
Context2 strategyB = new Context2("strategyB");
strategyB.printTheS10Result();
Context2 strategyB = new Context2("strategyC");
strategyB.printTheS10Result();
}
}
结合工厂模式与策略模式之后,对于高层模块而言不需要知道具体策略角色,只要通过Context就可以实现策略模式,我们只需要定义好规则,如本例中Context构造方法中传入”strategyA“,表示调用ConcreateStrategyA的策略方法,传入”strategyB“表示调用ConcreateStrategyB的策略方法。
本人会经常更新博客,并在文章附上更新时间! 转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weipinggg/article/details/109231542
欢迎大家关注我的公众号:frost2