模板模式(TemplateMethod)和策略(StrategyMethod)模式
模板模式和 策略模式使用场景类似,都是把算法进行封装,可以用分离高层算法和低层的具体实现细节。都允许高层算法独立于他的具体实现细节的重用。但是实现方式不同,在实现方式上,模板模式使用的是继承,策略模式则使用的委托。
模板模式比较老,缺点是具体的实现和通用的算法紧密的耦合在了一起,这样的话具体的一个实现只能被一个算法操纵。父类的的信息更多的暴露给子类。
而策略模式是委托的经典用法。策略模式消除了通用的一个算法和具体实现的耦合,使得具体的实现可以被多个通用的算法操纵。但是策略模式同样的,增加了类的层次和额外的复杂性,以内存和运行时间开销作为代价。
模板模式的UML静态图
模板模式展示了面向对象编程中诸多经典重用形式的一种。其中,通用算法被放置在基类中,并且通过继承在不同的具体上下文中实现该通用算法。但是这项技术是有代价的,继承是一种非常强的关系。派生类不可避免的要和他们的基类捆绑在一起。
接下来用一个示例来演示模板模式的用法:
public abstract class Application { protected abstract void init(); protected abstract void idle(); protected abstract void cleanup(); private boolean isDone = false; protected void setDone(){ isDone = true; } protected boolean done (){ return isDone; } /** * 模板模式,把需要执行的方法封装到抽象方法中 */ public void run(){ init(); while(!done()){ idle();; } cleanup(); }}
父类把子类需要实现的方法作为抽象方法,然后把通用的算法进行封装,封装到了run方法中
/** * Created by wangtf on 2015/11/19. * 模板模式 */public class FtocTemplateMethod extends Application{ private InputStreamReader in; private BufferedReader br; @Override protected void init() { in = new InputStreamReader(System.in); br = new BufferedReader(in); } public static void main(String[] args) { (new FtocTemplateMethod()).run(); } @Override protected void idle() { String fahrString = readLineAndReturnNullIfError(); if(fahrString ==null ||fahrString.length()==0){ setDone(); }else { System.out.println("to do something:" + fahrString); } } @Override protected void cleanup() { System.out.print("ftoc exit!"); } private String readLineAndReturnNullIfError(){ String s ; try{ s = br.readLine(); }catch (IOException e){ s = null; } return s; }}
子类继承父类,实现父类中的抽象方法,具体的算法被封装到了父类中。
策略模式的UML静态图
策略模式使用了一种非常不同的方法类倒置通用算法和具体实现之间的依赖关系
用示例演示策略模式:
/** * Created by wangtf on 2015/11/19. */public interface Application { public void init(); public void idle(); public void cleanUp(); public boolean done();}
定义接口
/** * 策略模式 * Created by wangtf on 2015/11/19. */public class FtosStrategy implements Application{ private InputStreamReader in; private BufferedReader br; private boolean isDone = false; @Override public void init() { in = new InputStreamReader(System.in); br = new BufferedReader(in); } @Override public void idle() { String fahrString = readLineAndReturnNullIfError(); if(fahrString ==null ||fahrString.length()==0){ setDone(); }else { System.out.println("to do something:" + fahrString); } } @Override public void cleanUp() { System.out.print("ftoc exit!"); } protected void setDone(){ isDone = true; } @Override public boolean done() { return isDone; } private String readLineAndReturnNullIfError(){ String s ; try{ s = br.readLine(); }catch (IOException e){ s = null; } return s; }}
实现接口中的内容
/** * Created by wangtf on 2015/11/19. */public class ApplicationRunner { private Application app ; ApplicationRunner(Application app){ this.app = app; } public void run(){ app.init(); while(!app.done()){ app.idle(); } app.cleanUp(); }}
ApplicationRunner中封装算法(Run方法),这样算法和具体的对象得以脱离。
实际上一个更恰当的例子是一个排序的例子,例如一个冒泡排序,通常有以下步骤
1、遍历数组(排序算法)
2、比较两个数字的大小
3、交换两个数字的位置
这样如果直接写的话,比较两个数字大小和交换数字的位置将会直接依赖于排序算法,这样的话我们只能排序数字,这样的话就不能把算法脱离开。解决方案是,把2 和3步骤进行抽取,算法独立,抽取出来以后,我们不光能比较两个数字,同样的能比较对象,比较字符串等等一切我们想要排序东西。这样就可以达到了算法复用的效果。