一、概述
设计模式也是面试的常见考点之一,这部分的知识相对比较固定,但由于有23种,如果把每一种都面面俱到的掌握也不太实际,本文主要是针对一些常见的设计模式,分析其在JDK,Spring, SpringMVC或Mybatis中的经典应用来进行分析,从而加深理解,让这一块的面试不再是问题。
二、设计模式分类
这23种设计模式可以为分三类,每一类都有常用和不常用的,整理如下:
分类 | 常用 | 不常用 |
---|---|---|
创建型 | 单例、工厂方法、抽象工厂、建造者 | 原型 |
结构型 | 适配器、装饰器、代理、组合 | 享元、门面、桥接 |
行为型 | 策略、模板、观察者、责任链、迭代器、状态 | 访问者、备忘录、命令、解释器、中介 |
接下来,本文会对一些常用的设计模式,给出理论和实现的对比。
三、单例模式
1、理论介绍
单例是最常使用的设计模式之一,相对来说比较好理解,一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
2、系统实现
单例的实现有很多种方式,比如说懒汉式,饿汉式,双重检查,枚举等。我们用Spring中的实现来进行分析,Spring中的Bean默认的生命周期都是单例,其获取实例的方法如下:
/**
* 本段代码来自 DefaultSingletonBeanRegistry.java
**/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//如果singletonObject 为空,则同步获取
synchronized (this.singletonObjects) {
//从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 如果仍然为空,则通过三级缓存的对象去创建。
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这段代码用于获取一个单例对象,只不过分别从三类缓存中获取(以解决循环依赖)。
四、工厂模式
1、理论介绍
工厂模式包括简单工厂、工厂方法和抽象工厂,不过简单工厂并不属于23种模式之一,因为同样是基于工厂接口创建实例,其对于开闭原则支持得不够好,而工厂方法相对来说显得更加合理。这两者的区别主要在于简单工厂把创建不同类型实例的逻辑放在创建方法里,而工厂方法则是通过多个工厂来实现。抽象工厂则是对于工厂方法的升级,允许一个工作有多个方法,创建多个不同类型的产品。
2、系统实现
工厂方法在框架中运用比较普遍,我们以Spring的FactoryBean为例,其定义如下:
//工厂定义
public interface FactoryBean<T> {
//创建类型为T的产品方法
T getObject() throws Exception;
// 省略其它代码
}
在Spring的生命周期中,如果bean的类型是FactoryBean,则创建bean实例是通过调用工厂类的getObject而完成的,FactoryBean为工厂,T为产品类型。实现简单举例如下:
public class ListFactoryBean extends AbstractFactoryBean<List<Object>> {
@Nullable
private List<?> sourceList;
@SuppressWarnings("rawtypes")
@Nullable
private Class<? extends List> targetListClass;
@Override
@SuppressWarnings("unchecked")
protected List<Object> createInstance() {
if (this.sourceList == null) {
throw new IllegalArgumentException("'sourceList' is required");
}
List<Object> result = null;
if (this.targetListClass != null) {
result = BeanUtils.instantiateClass(this.targetListClass);
}
else {
result = new ArrayList<>(this.sourceList.size());
}
Class<?> valueType = null;
if (this.targetListClass != null) {
valueType = ResolvableType.forClass(this.targetListClass).asCollection().resolveGeneric();
}
if (valueType != null) {
TypeConverter converter = getBeanTypeConverter();
for (Object elem : this.sourceList) {
result.add(converter.convertIfNecessary(elem, valueType));
}
}
else {
result.addAll(this.sourceList);
}
return result;
}
}
其中 AbstractFactoryBean 实现了FactoryBean,并暴露了createInstance 方法,而具体的FactoryBean只需要继承AbstractFactoryBean即可。
五、建造者模式
1、理论介绍
构造器模式将构造一个产品拆分为多个部分,而多个部分的构造顺序是可以任意组装的,通过不同方式的组装就得到不同形式的产品,和搭积木的方式是非常类似的,而对于客户端来说,不需要关注各类细节。
2、系统实现
建造者通常的实现是,在目标类的内部定义一个创建者类,在生成目标类时,先给这个对象设置各个参数,最后再通过build方法来返回一个目标类的实例。在mybatis的实现中非常喜欢使用创建者模式,以下是MappedStatement的实现细节:
//创建builder
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
//通过builder创建目标类的实例
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
六、适配器模式
1、理论介绍
这个也比较好理解,其思想就是通过定义一个转换类,去兼容历史的,已经无法修改的实现。通过这种方式,我们可以在不改变原有实现的情况下,通过另一种(通常是统一的标准接口)来调用。
2、系统实现
适配器的应用场景比较多,典型的如InputStreamReader,SpringMVC的Handler,这里我们以SpringMVC的hanlder为例。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView mv = null;
try {
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 省略一些代码
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
}
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
}
}
//Handler的实现之一
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
不同的请求URL,可能会对应不同类型的的处理器,而不同类型的处理器的处理方法也各不相同,为了能在DispatcherServlet的主流程中统一处理,所以SpringMVC就通过Handler的方式进行了适配,向DispatcherServlet屏蔽了差异。
七、组合模式
1、理论介绍
有的时候我们的对象是有层次的,比如一棵树及树上的节点,对于树来说它由叶子组成,但对于使用者来说,无论是树还是叶子,都被看做是一个节点。这种情况下,使用组合模式,可以对外帮我们屏蔽使用上的差异,对内又可以建立一个组合的关系。
2、系统实现
组合模式的使用场景也比较多,我们以mybatis里的动态sql实现为例,有一些sql是直接给出解析逻辑,这类为叶子节点,而有一些的内容又由其它节点组成,需要依赖其它节点的解析结果,以<choose>节点的对应节点类型如下:
public class ChooseSqlNode implements SqlNode {
private SqlNode defaultSqlNode;
private List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
八、代理模式
1、理论介绍
代理模式的原理和代码实现都不难掌握。它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能,和装饰模式不一样,除了增加一些附加功能,也用做对于调用方做一些调用限制。
2、系统实现
代理模式使用非常常见,最经典的莫过于JDK的动态代理,但这里我们还是以Spring的AOP的实现来看代理模式的应用,如下:
//AbstractAutoProxyCreator.java
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
AbstractAutoProxyCreator 是一个BeanPostProcessor,它可以在bean的生命周期对bean进行处理,在满足条件的情况下生成代理类。
九、装饰模式
1、理论介绍
当我们要对一个类进行增强,最直接的方式是定义其子类,但这种方式一方面需要生成新的类,另一方面不利于功能的灵活搭配,这种情况下,我们需要使用装饰模式,以实现在不改变现已有类的情况下,完成功能增强,同时装饰器之间还可以任意组合以实现不同的效果。
2、系统实现
装饰器的模式使用情况太普遍了,这里我们以JDK的IO流的缓存流机制为例来进行说明,InputStream有很多实现,而BufferredInputStream则是为各类输入流提供了缓存功能,如下:
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
/**
* Creates a <code>BufferedInputStream</code>
* with the specified buffer size,
* and saves its argument, the input stream
* <code>in</code>, for later use. An internal
* buffer array of length <code>size</code>
* is created and stored in <code>buf</code>.
*
* @param in the underlying input stream.
* @param size the buffer size.
* @exception IllegalArgumentException if {@code size <= 0}.
*/
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
从上面的代码看,在保留输入流的基础上,还增加了一个buf数组,具体的缓存实现就不分析了。
十、责任链模式
1、理论介绍
在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
2、系统实现
Spring 和 Mybatis 都有对于责任链模式的应用,责任链有两种方式,一种是链上的每个对象都会处理,二是链上的某一个对其进行处理,比如BeanPostProcessor,就是第二种的实现方式,而前面提到的Handler,就是第一种方式,我们再看下其实现:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
//有一个处理成功就结束
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
十一、策略模式
1、理论介绍
当我们去实现一个功能的时候,有可能同时存在多种不同的实现方式,而具体选取何种实现方式,可能取决于调用时的参数或者上下文,通常来说我们可以使用if-else来做选择,但这样会导致分支比较多,而且改动比较麻烦,这种情况下,策略模式为我们提供了优雅的处理方式。
2、系统实现
策略模式的应用也比较常见,JDK的线程池的拒绝策略,就是非常明显的策略模式应用,如下:
/**
* A handler for rejected tasks that runs the rejected task
* directly in the calling thread of the {@code execute} method,
* unless the executor has been shut down, in which case the task
* is discarded.
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
十二、模板模式
1、理论介绍
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
2、系统实现
模板模式在实现时,通常是其中某个方法定义为默认实现,有需要的子类去重写,以spring的refresh方法为例,如下:
//AbstractApplicationContext.java
@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();
//默认实现.
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) {
// 省略。。。
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
//AbstractRefreshableWebApplicationContext.java
protected void onRefresh() {
this.themeSource = UiApplicationContextUtils.initThemeSource(this);
}
十三、迭代器模式
1、理论介绍
迭代器模式,也叫作游标模式,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
2、系统实现
JDK自身实现了迭代器模式,对于一些容器的遍历来说,使用迭代器遍历可以方便地改变遍历策略,比如ArrayList的迭代器如下:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// ...
}
十四、总结
以上是对一些常见设计模式的总结与在框架中的应用,还有一些比较生僻,用得较少的,就没再列出来,但总的来说,如果这些都掌握了,外加对六大原则有所了解,应付面试应该是足够了,不过这个不是目的,我们在实际开发中,在适当的情况下,要合理利用各类设计模式。