- 设计模式解析一 工厂模式的不同
- 设计模式解析二 结构模式三剑客
- 设计模式解析三 行为模式三剑客
- 设计模式解析四 模板方法模式和外观模式
- 设计模式解析五 观察者模式和桥接模式
- 设计模式解析六 单例模式
一. 前言
讲到第四篇,其实已经把最常用的设计模式讲了一半了,今天讲策略模式的表兄弟,模板方法模式。
二. 模板方法模式
模板方式模式会被称为策略模式的兄弟的原因,是大家都封装算法,但策略模式注重的是封装一整个算法族,他的抽象类只有一个接口。但模板方法模式不同,他封装的是算法的一部分,抽象类里定义好了整个算法的模板,而其中的一部分算法则交由子类实现。
还是来举个蒸包子的例子,蒸包子我们来拆分一下,看看有哪些步骤:
- 和面
- 准备包子馅
- 包包子
- 蒸
我们大致将蒸包子的步骤分为上面四个,有哪些步骤是一样的呢?可以任务和面的步骤都是一样的,蒸的步骤是一样的,但是准备包子馅不一样,有猪肉的,牛肉的,梅干菜的,豆沙的。包的动作也不太一样,有的普通大包子,有的是小笼包。
那么我们把动作一样的部分写死,把可变的部分提供为抽象来供大家实现,是不是就能蒸出好吃的包子了呢?
接下来写代码:
public abstract class AbstractSteamedBun {
public final void steameBun() {
kneadDough();
prepareStuffing();
wrapBun();
steam();
}
protected abstract void wrapBun();
protected abstract void prepareStuffing();
private void kneadDough() {
System.out.println("和面...");
}
private void steam() {
System.out.println("开始蒸包子");
}
}
姐下面我们就来蒸个牛肉包:
public class SteamedBeefBun extends AbstractSteamedBun {
@Override
protected void wrapBun() {
System.out.println("开始包牛肉包子");
}
@Override
protected void prepareStuffing() {
System.out.println("开始准备牛肉包子馅");
}
}
运行:
public static void main(String[] args) {
AbstractSteamedBun bun = new SteamedBeefBun();
bun.steameBun();
}
和面...
开始准备牛肉包子馅
开始包牛肉包子
开始蒸包子
这就是模板方法模式,模板方法模式的应用也很广,很多地方随处可见,
比如最常见的排序Collections.sort(list)
,我们经常使用这个排序工具类,我们应该从没想过他的排序是用的什么算法,他是怎么排的,我们只知道我们需要让我们的实体实现Comparable
接口,实际上他的内部把整个排序的逻辑步骤都为我们固定好了,我们只需要实现值的比较即可,只是这里把这个模板方法设计成了接口跟算法进行了分离。
再有Spring框架的容器加载过程也有一个模板方法设计模式,Spring的抽象容器类AbstractApplicationContext
的源码:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
其中的obtainFreshBeanFactory()
方法中有调用一个refreshBeanFactory()
方法,该方法是一个抽象方法。
然后postProcessBeanFactory(beanFactory)
也是一个空实现的方法,
onRefresh()
方法也是一个空实现的方法,这些方法都是等待子类来实现的方法,尽管是非必要的。
定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法架构的情况下,重新定义算法中的某些步骤。
三. 外观模式
外观模式和模板方法模式没什么关系,写在这只是因为觉得一章就写一个设计模式有点太少了.。
其实这个模式跟适配器模式反而有一点相似,适配器模式通过把一个接口适配成另一个接口,目的是为了让不兼容的两个系统的接口能够运行。而外观模式也改变接口,但是他改变的接口的原因是为了简化接口,之所以这么称呼,是因为他将一个或数个类的复杂的一切都隐藏在背后,只露出一个干净美好的外观。
又到了有趣的举例子环节,我是个小米智能家居的拥捧者,家里也买了很多小米的智能家居,我们家客厅有一个孔灯,亮度较暗,还有一个大灯,非常亮,平时就开孔灯,偶尔就打开大灯然后关闭孔灯,都是可以通过小爱同学来进行控制的,所以我的日常是这样:
- 当我的孔灯亮着想开大灯的时候我会这样
小爱同学,打开大灯
小爱同学,关闭孔灯 - 当我的大灯亮着我想开孔灯的时候我会这样
小爱同学,打开孔灯
小爱同学,关闭大灯
但是用着用着我就觉得好麻烦啊,总是我的目标就是开一个灯,关另一个灯,能不能就喊一条指令啊,有一天突然发现小米有一个可以设置组合指令的地方:
配置了这个,然后在训练小爱同学收到开孔灯指令的时候就执行这个指令,以后再也不用每次都要喊两个指令那么麻烦了。
那么这个组合指令对于我们的小爱同学或者说对于我们来说就是一个外观模式,我们调用者不关心你内部复杂的细节,只关心我调用后要达到的效果,至于内部你是执行了一条指令,还是执行了两条指令对于调用者来说并不重要,引用百度百科对外观模式的评价:
- 实现了子系统与客户端之间的松耦合关系。
- 客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
另外再举个例子,我们经常使用日志系统,日志系统有一个组件叫做 slf4j,大名鼎鼎了,那么大家有没有想过为什么会取这样的一个名字?
其实他的全名是:Simple Logging Facade for Java。为java提供的简单日志外观,从名字就告诉了我们,这是一个外观模式,实际上slf4j本身并没有打印日志的能力,他只是定义了一套日志打印的接口Logger
接口还有ILoggerFactory
接口等,真正打印日志的还是 log4j
和logback
。
我们来看一下日志系统家族:
slf4j和common-logging都是日志接口,而后面的才是实现,那么为什么叫外观模式呢
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Application.class);
这句代码是我们通常获取日志打印对象的代码,使用的都是slf4j包中的类,在
LoggerFactory.getLogger
中,它会为我们取寻找logger的实现和装载过程,从而我们对我们隐藏内部的日志打印细节,所以我们才能这么方便的使用日志打印系统,讲到这里就再深入的去看一下里面的源码:
public static Logger getLogger(Class> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
重点在getILoggerFactory
方法中,如果我们去翻找logback或者log4j就能找到,它里面都有一个工厂类是实现了ILoggerFactory的,而且他们的日志打印类也是都是实现了Logger类的,你要说为什么他们会遵照这个标准,那是因为slf4j和logback和log4j都是一个人做的,膜拜大神。
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
}
在这里,会判断是否已经进行过日志容器初始化,如果没有初始化则会调用
performInitialization
方法进行初始化,在这个方法里有一个bind方法是重点。
private final static void bind() {
try {
Set staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
这里有一个findPossibleStaticLoggerBinderPathSet
方法,会去利用类加载器加载一个实现类
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
而这个实现类就是StaticLoggerBinder.class
,去看log4j和logback就会发现,这两个包都有这个类,在这个类在实例化的时候会创建一个ILoggerFactory
的实例,拿到这个工厂就可以创建各自的日志打印对象了。
你看,这个内部有很多复杂的东西,但是外观模式帮我们搞定以后,我们只需要调用一句话:
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Application.class);
这就是外观模式的魅力了。
定义
外观模式提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。