模板模式
模板模式的使用通常都是基于这样一种场合:整体工作流程已经知道,但是具体的业务处理得视情况而定。根据面向对象的设计原则,应该将系统中易于变化的部分隔离开来,进行抽象化,让具体的实现细节依赖于这个抽象。这样才能够满足开闭原则的要求,当产生新的变化时,对原有的系统不会造成影响。
模板模式将整体的工作流程定义在一个抽象父类中,将具体的业务处理声明为抽象函数。父类负责处理整个流程,而子类负责实现具体业务。一个示例的代码如下所示:
public interface Service{
public void workflow();
}
public abstract class BaseServiceImpl implements Service{
public void workflow(){
//execute some steps
// handle business
this.business();
//execute some steps
}
// 声明为protected,不能在外部直接调用
protected abstract void business(){}
}
public class ConcreteServiceImpl extend BaseServiceImpl {
protected abstract void business(){
// concrete handle process.
}
}
从代码中可看出,模板模式同时也是IOC原则的具体应用。父类中定义了完整的工作流程,具体子类并不直接调用父类,只需实现自己的业务即可,父类通过工作流程来调用子类的业务方法。通俗点说就是“dont call me, I'll call you”。不过在模板模式中抽象出来的是一个方法而不是一个真正的接口。
spring中模板模式的应用
spring中大量运用了模板模式,特别是在包装底层数据库访问方面,使用了大量的模板类,如JdbcTemplate,HiernateTemplate等。这些模板类封装了访问数据库的通用操作(如获取连接、释放连接等),将具体的业务访问代码抽象出来,让具体的应用自己去实现。如JdbcTemplate中一个execute方法实现如下:
public Object execute(StatementCallback action) throws DataAccessException {
Connection con = DataSourceUtils.getConnection(getDataSource());
// many other codes...
// 回调
Object result = action.doInStatement(stmtToUse);
return result;
}
execute方法使用了一个 StatementCallback 接口的实例为回调参数,在执行过程中,调用此实例的 doInStatement 方法来访问数据库。这种模板模式与前面介绍的传统的模板模式有所不同,这里并没有抽象出一个方法,然后让子类来实现此方法。而是将具体业务抽象成了一个接口,具体的应用实现此接口,并以此实现类的实例为参数来调用模板方法。在这一实现过程中,使用到了回调机制。
callback回调
回调机制简单说来就是我们将实现好的方法A(或者实例)传递给另一个方法B,并由方法B在执行过程中负责调用A。回调方式与普通调用方式不同之处就在于它是让别人来调用自己,而不是自己主动去调用别人,回调方法在方法B上注册了自己后(即传递给B后),主动权就落在了方法B上,回调方法本身也不知道自己何时会被调用。
上面代码中StatementCallback就是一个抽象出来的回调接口,模板方法利用此接口的实现来实现自身。一般而言在java中通常是通过匿名类来使用回调机制。因此,回调机制一般用于回调接口比较简单的情况,此时使用一个简单的匿名类可以简化调用代码。
回调机制在面向过程的编程中经常会见到,在面向对象的编程中他有了一个替代方案---命令模式,这个后面会介绍到。
其实,如果我们仔细查看代码中展示的回调机制的实现以及类之间的依赖关系的话,会发现回调机制其实就是策略模式的一种简化了的特殊情况,因为在这里抽象接口很简单所以可以直接使用回调机制。当接口变得很复杂时,那就得老老实实使用策略模式了。
策略模式
我们先来看看对策略模式的描述:
策略模式将具体的算法实现封装起来,使其对使用者不可见。也即将使用者和类的具体实现隔离开来。这样一来使用者不用知道算法的具体实现,即它不用依赖于具体;二来具体算法的变化对使用者来说也是透明的,增强了程序的健壮性。
在上面的例子中,StatementCallback接口就是抽象出来的算法接口,而我们在调用时使用的匿名类就是这个算法的具体实现。
策略模式和模板模式都是IOC的原则的具体应用,只是它们采用的是完全不同的方式。模板模式使用的是“is a”的关系,即在父类中定义抽象,在子类中具体实现,它是通过一个集成体系来实现的;而策略模式则使用的是“has a”的关系,一个类不再通过继承一个抽象类来实现具体应用,而是通过持有一个策略接口的引用来实现这一点。在调用的时候,通过给这个引用传入不同的具体实例而使其具有了不同的行为。OOP有个原则:“用引用代替继承,用has代替is ”。因为当一个类继承了另一个类的时候就说明这个类是某样东西,从而也就决定了它的行为。特别是在java这类单继承语言中,如果这些具体实现类要求有另外一个继承体系时,模板模式就不能够工作。因此最好尽可能用策略模式来代替模板模式。
状态模式
说到了策略模式,就不可能不提到状态模式。这两个模式的UML图示一模一样的,主要的差别应该是在应用场景方面。
对状态模式的描述如下:
状态模式允许一个对象在多个状态之间进行转换,并且这些状态之间的转换是通过某些规则定义好的。一个典型的例子就是工作流状态机。因此,“状态的隐式转换”应该是状态模式的重点。而策略模式关注的是在诸多策略中选取所需的策略,也即通常一个对象只有一个策略,并且通常不会改变。虽然策略模式的实现机制使得我们可以很容易地改变对象的实现策略,但需要显示进行修改。而且策略之间并没有任何关联,用户可以随意在不同策略之间切换。
下面的代码展示了一个简单的状态模式的应用:
public interface State{
public void change(Context c);
}
public class ConcreteStateOne{
public void change(Context c){
c.setState(new ConcreteStateTwo());
}
}
public class ConcreteStateTwo{
public void change(Context c){
c.setState(new ConcreteStateOne());
}
}
public class Context{
private State state;
public void setState(State s){
this.state = s;
}
public void change(){
this.state.change(this);
}
}
Context类有两个状态,每当执行一次change时将状态改变为另一个。注意这里State接口的change方法有一个Context对象引用的参数。这里和访问者模式的实现还是有几分相似的,Context(composite)对象将自己专递给自己的状态对象(Visitor对象),然后由状态对象来负责State的变更。