策略模式和模板方法模式-转载
策略模式结合模板方法模式
??????? 在实际应用策略模式的过程中,经常会出现这样一种情况,就是发现这一系列算法的实现上存在公共功能,甚至这一系列算法的实现步骤都是一样的,只是在某些局部步骤上有所不同,这个时候,就需要对策略模式进行些许的变化使用了。
??????? 对于一系列算法的实现上存在公共功能的情况,策略模式可以有如下三种实现方式:
??????? 更进一步,如果这个时候发现“一系列算法的实现步骤都是一样的,只是在某些局部步骤上有所不同”的情况,那就可以在这个抽象类里面定义算法实现的骨架,然后让具体的策略算法去实现变化的部分。这样的一个结构自然就变成了策略模式来结合模板方法模式了,那个抽象类就成了模板方法模式的模板类。
??????? 在上一章我们讨论过模板方法模式来结合策略模式的方式,也就是主要的结构是模板方法模式,局部采用策略模式。而这里讨论的是策略模式来结合模板方法模式,也就是主要的结构是策略模式,局部实现上采用模板方法模式。通过这个示例也可以看出来,模式之间的结合是没有定势的,要具体问题具体分析。
??????? 此时策略模式结合模板方法模式的系统结构如下图5所示:
??????????????????????????????????? 图5? 策略模式结合模板方法模式的结构示意图
??????? 还是用实际的例子来说吧,比如上面那个记录日志的例子,如果现在需要在所有的消息前面都添加上日志时间,也就是说现在记录日志的步骤变成了:第一步为日志消息添加日志时间;第二步具体记录日志。
(1)记录日志的策略接口没有变化,为了看起来方便,还是示例一下,示例代码如下:?
?
/**
?* 日志记录策略的接口
?*/
public interface LogStrategy {
??? /**
??? ?* 记录日志
??? ?* @param msg 需记录的日志信息
??? ?*/
??? public void log(String msg);
}
(2)增加一个实现这个策略接口的抽象类,在里面定义记录日志的算法骨架,相当于模板方法模式的模板,示例代码如下:?
?
/**
?* 实现日志策略的抽象模板,实现给消息添加时间
?*/
public abstract class LogStrategyTemplate implements LogStrategy{
??? public final void log(String msg) {
?????? //第一步:给消息添加记录日志的时间
?????? DateFormat df = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss SSS");
?????? msg = df.format(new java.util.Date())+" 内容是:"+ msg;
?????? //第二步:真正执行日志记录
?????? doLog(msg);
??? }
??? /**
??? ?* 真正执行日志记录,让子类去具体实现
??? ?* @param msg 需记录的日志信息
??? ?*/?????????????????????????????????
??? protected abstract void doLog(String msg);
}
(3)这个时候那两个具体的日志算法实现也需要做些改变,不再直接实现策略接口了,而是继承模板,实现模板方法了。这个时候记录日志到数据库的类,示例代码如下:?
?
/**
?* 把日志记录到数据库
?*/
public class DbLog extends LogStrategyTemplate{
????//除了定义上发生了改变外,具体的实现没变
??? public void doLog(String msg) {???
?????? //制造错误
?????? if(msg!=null && msg.trim().length()>5){
?????????? int a = 5/0;
?????? }
?????? System.out.println("现在把 '"+msg+"' 记录到数据库中");
??? }
}
同理实现记录日志到文件的类如下:?
?
/**
?* 把日志记录到数据库
?*/
public class FileLog extends LogStrategyTemplate{
??? public void doLog(String msg) {
?????? System.out.println("现在把 '"+msg+"' 记录到文件中");
??? }
}
(4)算法实现的改变不影响使用算法的上下文,上下文跟前面一样,示例代码如下:?
?
/**
?* 日志记录的上下文
?*/
public class LogContext {
??? /**
??? ?* 记录日志的方法,提供给客户端使用
??? ?* @param msg 需记录的日志信息
??? ?*/
??? public void log(String msg){
?????? //在上下文里面,自行实现对具体策略的选择
?????? //优先选用策略:记录到数据库
?????? LogStrategy strategy = new DbLog();
?????? try{
?????????? strategy.log(msg);
?????? }catch(Exception err){
?????????? //出错了,那就记录到文件中
?????????? strategy = new FileLog();
?????????? strategy.log(msg);
?????? }
??? }??
}
(5)客户端跟以前也一样,示例代码如下:?
?
public class Client {
??? public static void main(String[] args) {
?????? LogContext log = new LogContext();
?????? log.log("记录日志");
?????? log.log("再次记录日志");
??? }
}
??????? 运行一下客户端再次测试看看,体会一下,看看结果是否带上了时间。
??????? 通过这个示例,好好体会一下策略模式和模板方法模式的组合使用,在实用开发中是很常见的方式。
避免多重条件语句
??? 根据前面的示例会发现,策略模式的一系列策略算法是平等的,可以互换的,写在一起就是通过if-else结构来组织,如果此时具体的算法实现里面又有条件语句,就构成了多重条件语句,使用策略模式能避免这样的多重条件语句。
??? 如下示例来演示了不使用策略模式的多重条件语句,示例代码如下:
?
?
public class OneClass {
??? /**
??? ?* 示范多重条件语句
??? ?* @param type 某个用于判断的类型
??? ?*/
??? public void oneMethod(int type){??????
//使用策略模式的时候,这些算法的处理代码就被拿出去,
//放到单独的算法实现类去了,这里就不再是多重条件了
?
?????? if(type==1){??????????????????
?????????? //算法一示范
?????????? //从某个地方获取这个s的值
?????????? String s = "";
?????????? //然后判断进行相应处理
?????????? if(s.indexOf("a") > 0){
????????????? //处理
?????????? }else{
????????????? //处理
?????????? }
?????? }else if(type==2){
?????????? //算法二示范
?????????? //从某个地方获取这个a的值
?????????? int a = 3;
?????????? //然后判断进行相应处理
?????????? if(a > 10){
????????????? //处理
?????????? }else{
????????????? //处理
?????????? }
?????? }
??? }
}
更好的扩展性1:策略模式的本质
??????? 策略模式的本质:分离算法,选择实现。
??????? 仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封装隔离带来的好处。也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关心具体的策略实现,也可以通过使用不同的实现类来实例化接口,从而实现切换具体的策略。
??????? 看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时候,会大大增加客户端使用的难度。因此,引入上下文还是很必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让整个设计模式更独立、更有整体性,也让客户端更简单。
??????? 但纵观整个策略模式实现的功能和设计,它的本质还是“分离算法,选择实现”,因为分离并封装了算法,才能够很容易的修改和添加算法;也能很容易的动态切换使用不同的算法,也就是动态选择一个算法来实现需要的功能了。
2:对设计原则的体现
??????? 从设计原则上来看,策略模式很好的体现了开-闭原则。策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能很容易的把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。这在前面的示例中已经体现出来了,好好体会一下。
??????? 从设计原则上来看,策略模式还很好的体现了里氏替换原则。策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个父类。这样只要使用策略的客户保持面向抽象类型编程,就能够使用不同的策略的具体实现对象来配置它,从而实现一系列算法可以相互替换。
3:何时选用策略模式
??????? 建议在如下情况中,选用策略模式: