1.策略模式
定义一系列算法,把他们独立封装起来,并且这些算法之间可以相互替换。策略模式主要是管理一堆有共性的算法,策略模式让算法独立于使用它的客户而变化,客户端可以根据需要,很快切换这些算法,并且保持可扩展性。
策略模式的本质:分离算法,选择实现。
2.UML类图
策略模式结构中包括三种角色:
- 策略(Strategy):策略是一个接口,该接口定义若干个算法标识,即定义了若干个抽象方法。
- 具体策略(ConcreteStrategy):具体策略是实现策略接口的类,具体策略实现策略接口所定义的抽象方法,即给出算法标识的具体算法。
- 上下文(Context):上下文是依赖于策略接口的类,即上下文包含有策略声明的变量。上下文中提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法。
3.举例说明
就拿程序员举例吧,假如现在我要写一个程序,我们可以使用java、php。那到底使用哪种语言呢?使用我们的简单工厂模式完全可以解决这个问题
1、抽象语言类
参考:
public interface CodeLanguage { void useLanguage(); }
2、具体语言类
public class CodeJava implements CodeLanguage { @Override public void useLanguage() { System.out.println("使用Java语言编程"); } }
public class CodePhp implements CodeLanguage { @Override public void useLanguage() { System.out.println("使用Php语言编程"); } }
3、工厂类
public class LanguageFactory { public static CodeLanguage getLanguage(String type){ CodeLanguage codeLanguage=null; switch (type){ case "java": codeLanguage=new CodeJava(); break; case "php": codeLanguage=new CodePhp(); break; } return codeLanguage; } }
虽然问题解决了,但正如我们所说的简单工厂模式的缺点一样:
- 工厂类负责所有对象的创建逻辑,该类出问题整个系统无法运行。
- 系统扩展困难,一旦添加新产品就不得不修改工厂方法。
- 由于使用了静态工厂方法,所以工厂角色无法形成基于继承的等级结构。
假如我们又要增加使用python语言,那么我们除了增加相应的语言类,还要修改工厂类,很明显这不是最佳的方法,而策略模式便是最好的选择。根据策略模式的定义,策略模式主要是管理一堆有共性的算法,这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据“里氏代换原则”和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。对比简单工厂模式,我们发现,简单工厂模式是利用工厂类根据参数来动态的生成具体产品类,然后单独的调用具体产品类的方法;而策略模式是由客户端创建这些具体算法类,然后交由上下文来调用这些具体算法类中的方法。
策略类接口
public interface CodeLanguage { void useLanguage(); }
具体策略类
public class CodeJava implements CodeLanguage { @Override public void useLanguage() { System.out.println("使用Java语言编程"); } }
public class CodePhp implements CodeLanguage { @Override public void useLanguage() { System.out.println("使用Php语言编程"); } }
这点和简单工厂模式是抽象产品和具体产品代码是一样的,下面是不同点,实现一个上下文对象
public class CodeContext { CodeLanguage codeLanguage; public CodeContext1(CodeLanguage codeLanguage) { this.codeLanguage = codeLanguage; } void useLanguage(){ codeLanguage.useLanguage(); } }
使用模式
public class Application { public static void main(String[] args) { CodeContext context; context = new CodeContext(new CodeJava()); context.useLanguage(); context = new CodeContext(new CodePhp()); context.useLanguage(); }}
上面是一个最简单的策略模式的实现方式,按照功能分为3个部分,定义策略抽象接口,然后根据具体算法实现策略接口,最后需要定义一个上下文对象。这里的上下文对象主要用来切换算法,上下文对象里面也是针对接口编程,具体算法实现被封装了。
4.策略模式的理解
上面实现的只是一种最简单的策略模式的框架,实际应用的时候,我们可以针对不同情况修改上下文对象和具体的算法实现。比如说,可以增加一个抽象类实现作为算法模板。抽象类里面我们可以封装一些公共的方法。这样实现具体的算法的时候,每个算法公共部分就被分离出来。
策略模式的目的是把具体的算法抽离出来,把每个算法独立出来。形成一系列有共同作用的算法组,然后这个算法组里面的算法可以根据实际情况进行相互替换。
策略模式的中心不是如何实现这些算法,而是如何组织和调用这些算法。也就是把我们平时写到一块的算法解耦出来,独立成一个模块,增强程序的扩展性。
策略模式里面的算法通常需要数据执行,我们可以根据实际情况把数据放在不同地方。例如可以放在上下文类里面,然后每个算法都可以使用这些数据。或者对接口封装一个抽象类,在抽象类里面添加数据。这些可以根据实际的情况综合考虑。设计模式里面没有一成不变的万能模式,每种模式都有变化版本,需要根据实际的项目进行变通。
5.策略模式优缺点
策略模式的优点:
策略模式完全符合“开放-封闭原则”。
策略模式提供了管理相关算法族的办法。恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切换。
使用策略模式可以避免多重条件选择语句。
更好的扩展性:在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在选择使用策略的地方选择使用这个新的策略实现就好了。
策略模式缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类,而且这样也暴露了策略的具体实现。
由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会相应增多。
策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用。
6.模式优化
考虑到策略模式在使用的时候会过多的暴露策略类,代码的耦合性过高,结合简单工厂模式进行优化,对上下文类进行修改
public class CodeContext { CodeLanguage codeLanguage; public CodeContext(String type) { switch (type){ case "java": CodeJava codeJava=new CodeJava(); codeLanguage=codeJava; break; case "php": CodePhp codePhp=new CodePhp(); codeLanguage=codePhp; break; } } void useLanguage(){ codeLanguage.useLanguage(); } }
使用模式
CodeContext context = new CodeContext("java"); context.useLanguage(); context = new CodeContext("php"); context.useLanguage();
相比传统策略模式,我们把直接创建具体上下文对象改通过参数判断来生成,这样暴露给客户端的只有上下文对象,代码的耦合度更低,可以说这既是简单工厂模式的升级,也是策略模式的升级。