设计模式实践(一)-框架源码中的常见设计模式

前言

设计面向对象比较困难,而设计可复用的面向对象就更加困难!
设计模式的不是为了提高代码的运行效率,而是提高开发效率!
设计模式使代码编写真正工程化,是软件工程的的基石脉络!
设计模式是软件开发的葵花宝典,天下武功,无坚不摧,唯快不破!

设计模式实践(一)-框架源码中的常见设计模式_第1张图片
设计模式实践(一)-框架源码中的常见设计模式_第2张图片

一、装饰器模式

通过组合的方式动态地给一个对象添加一些额外的职责或者行为

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责,很契合设计模式原则之一开闭原则

  • 当对象族实现复杂度已经无法用继承来实现(eg:可能有大量独立的扩展,

  • 装饰类增加了系统的扩展性,对单元测试友好

为支持每一种组合将产生大量的子类,使得子类数目呈现组合叠加爆炸性增长)

  • 源码套路
X x=new X1(new X2(new X3()))......

UML类图

设计模式实践(一)-框架源码中的常见设计模式_第3张图片
image

一层一层嵌套,"装饰器类" 持有目标对象的引用,具体方法执行委托给具体的目标对象子类形成了一连串"装饰器链",不断地增强功能

应用场景1

mybatis 二级缓存
  • 抽象组件实现类 (cache.impl包下)


    设计模式实践(一)-框架源码中的常见设计模式_第4张图片
    WechatIMG3.jpeg
  • 装饰器族(cache.decorators包下)

设计模式实践(一)-框架源码中的常见设计模式_第5张图片
WechatIMG4.jpeg
  • build模式构建二级缓存
 Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();

  • 装饰器入口
  if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
 
  try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
四不四很符合X x=new X1(new X2(new X3()))......[呲牙]
  • 以FifoCache分析一下装饰器的使用
 public class FifoCache implements Cache {
  private final Cache delegate;
  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList();
    this.size = 1024;
  }
//put get实际上都委托给了PerpetualCache来实现
  public void putObject(Object key, Object value) {
    cycleKeyList(key);
    delegate.putObject(key, value);//增强了回收策略
  }
  public Object getObject(Object key) {
    return delegate.getObject(key);
  }
//增强的方法,先回收最先进入的换成对象
 private void cycleKeyList(Object key) {
    keyList.addLast(key);
    if (keyList.size() > size) {
      Object oldestKey = keyList.removeFirst();
      delegate.removeObject(oldestKey);
    }
  }
 
 

Cache的设计上目标类和装饰器类实现了Cache接口,装饰器一个一个装饰器类串起来,层层包装目标类形成一个链

增强的功能一览

  • ScheduledCache:根据时间间隔清理缓存数据
  • LoggingCache:缓存命中率打印(debug)
  • LruCache:最近最少使用原则
  • SoftCache:jdk软引用策略,jvm内存不足时回收缓存对象
  • 等等.....

应用场景2

JDK IO流
FileInputStream in=new FileInputStream(new File ("hello.txt"));

BufferedInputStream inBuffered=new BufferedInputStream (in);

BufferedInputStream是一层装饰,增强了“缓冲区”的功能....

等等.....


二、模板设计模式

定义一个操作中算法的骨架或流程,充分利用"多态"使得子类可以不改变算法的结构即可重新定义实现

适用场景

完成一件事情,有固定的流程步骤比如说 1->2->3->4,但是每个步骤根据子类对象的不同,而实现细节不同,就可以在父类中定义不变的方法,把可变的方法通过子类回调来实现

关键字: 回调

UML类图

设计模式实践(一)-框架源码中的常见设计模式_第6张图片
image
  • spring/mybatis/dubbo出现频率较高,一般表现为抽象类+protected+abstract方法组合
  • 代码套路
 public abstract class Abstractxxxxxx{
      
    public  void method{
         //校验逻辑
         //参数装配
//业务逻辑1、2、3
         doMethod();
         //异常处理
         //资源清理
    }
    //抽象方法由子类实现,父类回调...
    protected abstract void doMethod() ;
}

应用场景1

mybatis executor执行器

设计模式实践(一)-框架源码中的常见设计模式_第7张图片
20151220224146815.jpg
  • BaseExecutor 模板抽象父类
  • SimpleExecutor 简单执行器类,常规的CURD
  • ReuseExecutor 复用Statement执行器
  • BatchExecutor 批量update执行器类
  • 抽象类BaseExecutor 查询模板方法
  
  public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    一级二级缓存处理逻辑
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List list;
      queryStack++;
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //真正的查询db入口
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
   todo 一些清理方法,缓存,事务,连接关闭等等
   查询db方法入口
 private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      todo一堆前置方法 校验,缓存(不可变方法)
      doQuery是抽象方法,可变方法交给具体的子类去实现
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
      todo 一堆后置方法....(不可变方法)

//protected abstract 暗示着需要子类去实现
 protected abstract  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
     
update/delete/insert套路同上
  • 实现类SimpleExecutor,真正的执行器
public class SimpleExecutor extends BaseExecutor { 
  //真正查询db的入口实现
  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

方法调用,根据ExecutorType类型不同选择不同的实现类

 sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
 sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);

Executor选择器入口

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     //根据参数选择执行器实现 -> 策略模式
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      //上文装饰器的包装入口 -> 装饰器模式
      executor = new CachingExecutor(executor);
    }
   //插件拦截器链的入口-> 责任链模式
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

应用场景2

消息中间件 消费端-consumer处理消息
设计模式实践(一)-框架源码中的常见设计模式_第8张图片
  • 消费端子类实现类消费逻辑
 private class MyHandle extends AbstractHandle {
        //子类方法实现父类,在抽象父类模板方法回调之!
        public void doHandle(String data) {
            //消费解析消息 todo...
        }
    }
  • consumer抽象类模板方法
public abstract class AbstractHandle implements IHandle{
    public void handle(message msg)  {
        //todo 前置处理省略
            try {
                //抽象方法由子类实现后,回调...
                doHandle(data);
            } catch (  Exception ex) {
               //..todo
            }
        // 后置业务逻辑 todo 清理,日志,异常捕获等等
    }

三、策略模式

动态的改变对象的行为,实现某一个功能有多种算法或者策略,多种不同解决方案动态切换,起到改变对象行为的效果,一般会结合模板方法模式配合使用

  • UML类图


    设计模式实践(一)-框架源码中的常见设计模式_第9张图片

应用举例1

jdk线程池拒绝策略

线程池构造方法,其中RejectedExecutionHandler是拒绝策略的接口

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

策略接口

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

策略实现类


设计模式实践(一)-框架源码中的常见设计模式_第10张图片

策略实现之一:线程池满了,主线程执行

 public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

要实现自己的拒绝策略只需要实现拒绝接口即可

  • netty线程池拒绝策略
package org.jboss.netty.handler.execution;
private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
        NewThreadRunsPolicy() {
            super();
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                final Thread t = new Thread(r, "Temporary task executor");
                t.start();
            } catch (Throwable e) { }
        }
    }
  • dubbo线程模型线程池拒绝策略
package com.alibaba.dubbo.common.threadpool.support;
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
    
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED!");
        logger.warn(msg);
        throw new RejectedExecutionException(msg);
    }
}

应用举例2

消息中间件的消费模型
设计模式实践(一)-框架源码中的常见设计模式_第11张图片
  • 客户端根据实际情况选择消费策略算法
    客户端依赖抽象类,实现类依赖的抽象,一定程度上契合了依赖倒转原则客户端和实现类通过抽象类关联在一起。
 AbstractHandle handle = new MyHandle();
 handle.handle()

并行和串行子类实现handle()策略方法,从而对消息有不同的消费策略
增强其他的消费策略,实现抽象方法即可,开闭自如


四、外观模式

定义了一个高层接口,为一堆子系统接口提供一个一致的entry入口,供客户端调用

引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。


设计模式实践(一)-框架源码中的常见设计模式_第12张图片

可以看到经过外观模式改造后的子系统对于客户端来说,封装了内部负载的实现逻辑,对客户端友好

  • UML类图


    设计模式实践(一)-框架源码中的常见设计模式_第13张图片
  • 子系统:职责单一,易于维护
  • 门面:充当了客户类与子系统类之间的“第三者”,对外隐藏了很多细节,对客户的来说"最少知道",比较契合迪米特法则。

应用举例

SLF4J

SLF4J = Simple Logging Facade for Java

设计模式实践(一)-框架源码中的常见设计模式_第14张图片
  • slf4j相当于一层门面facade,日志框架的抽象,提供了对日志子系统(log4j,logback,jdk-logging)的入口

工厂模式获取当前类的日志对象,对于具体的实现,客户端是不知道的,实际使用是绑定具体实现类。
获取日志子系统实现类通过类加载机制,按照加载顺序,第一个被加载到的子日志系统就绑定到slf4j。
子类加载和寻通过maven pom.xml配置

private final Logger logger = LoggerFactory.getLogger(this.getClass());

设计模式实践(一)-框架源码中的常见设计模式_第15张图片

如图所示,门面slf4j-api
log4j:根据 桥接slf4j-log4j包适配 log4j ,输出由最终log4j.jar实现类执行。
logback:直接实现了slf4j的接口,不需要桥接包的适配过程
子系统通过类加载机制来绑定具体的日志子系统,"先到先得"

/*
很重要,类加载器加载路径,slf4j日志子系统都要有同名的包类
根据类加载器双亲委托机制,第一个加载到的日志子系统就优先使用
*/
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

  private static Set findPossibleStaticLoggerBinderPathSet() {
   
    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);
      }
     
  • logback.jar包同名类


    设计模式实践(一)-框架源码中的常见设计模式_第16张图片

观察者-事件监听模式

察者模式定义了对象之间的一对多的依赖关系,这样,当"一"的一方状态发生变化时,它所依赖的"多"的一方都会收到通知并触发相关操作

  • 关系图谱


    设计模式实践(一)-框架源码中的常见设计模式_第17张图片
    image.png
  • UML类图


    设计模式实践(一)-框架源码中的常见设计模式_第18张图片
  • event-object:事件状态对象,作为监听器的参数参与事件交互
  • event0source:具体的事件源,被监听的对象
  • event listener:事件监听器,由事件源出发监听事件,引起响应变化。

事件监听把事件源和监听器的的依赖关系彻底解耦
公众号订阅,订阅报纸期刊,数据库触发器等等都有观察者模式的"味道"
基于事件驱动机制的系统或语言,比如 js、netty,kafka等亦如此。
java-swing中的应用比较多 button点击事件 ....

应用场景1

cuodao-kafka-consumer事件监听
  • kafka循环pull数据 即为"事件源",该事件源是一个线程
public class PullTask extends Thread {
  • kafka-consumer状态变化 包装为"事件"
/**
 * 消费端工作者状态发生变化的事件
 * @author cuodao
 */
public class TaskStatusEvent extends EventObject {
    private static final long serialVersionUID = 1298884648955658019L;

    private final TaskStatus oldStatus;

    private final TaskStatus newStatus;
  • TaskStatus 事件添加对应监听器加以侦听其状态变化
   PullTask() {
            this.setDaemon(true);
            //添加监听器
            this.addListener(myListener());
        }
  • 事件状态变化后引起的事件回调,修改consumer的全局状态
        private TaskStatusListener myListener() {
            return new TaskStatusListener() {
                @Override
                public void statusListener(TaskStatusEvent e) {
                 
                    }

六、适配器模式

将一个类的接口转换成客户希望的另外一个接口

  • UMl类图(类适配器)


    设计模式实践(一)-框架源码中的常见设计模式_第19张图片
  • adpter也可以是抽象为接口,适配由子类实现
  • 对象适配器类图类似,adpter和adptee关系由继承->关联,也就是委托
    业务代码中,面向适配器开发可以拓展新功能,这里适配器模式也有一点装饰器的意思,只不过"动机"不同!一个是单纯的增强,一个是补偿

应用场景

mybatis 日志框架适配
设计模式实践(一)-框架源码中的常见设计模式_第20张图片
  • target顶级日志接口
package org.apache.ibatis.logging;

/**
 * @author Clinton Begin
 */
public interface Log {

  boolean isDebugEnabled();
  void error(String s, Throwable e);
  debug
  tarce
  warn
  .....
}
  • log4j适配器类,采用了对象适配-委托给"org.apache.log4j.Logger"
package org.apache.ibatis.logging.log4j;
import org.apache.ibatis.logging.Log;
import org.apache.log4j.Logger;

/**
 * @author Eduardo Macarron
 */
public class Log4jImpl implements Log {
  private Logger log;
  public void error(String s, Throwable e) {
    log.log(FQCN, Level.ERROR, s, e);
  }
 

其他日志适配器类也一样,继承"org.apache.ibatis.logging.Log"类,里面持有对第三方日志框架的日志记录类的引用

  • 客户端即加载 LogFactory类的时候,首先会去根据配置文件动态确定使用哪个第三方日志框架
 
        
    

解析xml配置文件的构造器代码截取

  Class logImpl = (Class)resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);

logfactory日志工厂构造获取具体的日志实现类


public final class LogFactory 
  public static final String MARKER = "MYBATIS";

  private static Constructor logConstructor;

  static {
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useSlf4jLogging();
      }
    });
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useCommonsLogging();
      }
    });
    
    tryImplementation(new Runnable() {
      @Override
      public void run() {
        useLog4JLogging();
      }
    });
  .....等等
 
private static void setImplementation(Class implClass) {
    try {
      Constructor candidate = implClass.getConstructor(String.class);
      Log log = candidate.newInstance(LogFactory.class.getName());
     
      }
      logConstructor = candidate;
    } catch (Throwable t) {
    
    }
  }

mybatis没有自己的日志系统,依赖于第三方实现,通过配置文件参数根据logfactory来适配对应的第三方日志系统(log4j,jdk-log,commonslogging)过程略,可参照slf4j日志适配模式


代理模式

为其他对象提供一种代理以控制对这个对象的访问,可能是框架源码使用频率最高的设计模式!

  • 应用场景:rpc框架客户端(httpinvoker/hessian/rmi),Aop,拦截器,mybatis动态生成mapper接口实现类,事务、连接池框架

  • 静态代理

  • 动态代理(通过对象反射动态生成代理类 jdk/cglib/asm/javassist)

实际应用以动态代理居多!动态代理本质上也是静态代理,只不过是以字节码增强,类加载器根据目标target类二进制文件,动态生成其代理代码,并载入jvm方法区,类名多以xxx$xxxx.class

  • UML类图


    设计模式实践(一)-框架源码中的常见设计模式_第21张图片
  • 协调调用者和被调用者,在一定程度上降低了系统的耦合度

应用场景1

dubbo-consumer
  • 解析dubbo自定义标签

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        //服务消费者标签bean
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        //服务提供者标签bean
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
      
    }
  • 注释惊见dubbo作者 梁飞
    设计模式实践(一)-框架源码中的常见设计模式_第22张图片

    梁飞技术博客地址:http://javatar.iteye.com/
/**
 * ReferenceFactoryBean
 * 
 * @author william.liangf  duubo作者 梁飞
 * @export
   解析dubbo自定义标签 实现了InitializingBean
   初始化标签信息的时候,向注册中心订阅服务,并生成consumer接口的动态代理类
 */
public class ReferenceBean extends ReferenceConfig implements  InitializingBean,x,x,x {
  public void afterPropertiesSet() throws Exception {
        //todo....... 
     createProxy()       
 }
private T createProxy(Map map) {
        //todo.....
     return (T) proxyFactory.getProxy(invoker);  
 }
  • dubbo动态代理工厂基于SPI思想,默认由"javassist"实现,
    关于 SPI插件机制我有空再给大家做个技术分享专题
/**
 * ProxyFactory. (API/SPI, Singleton, ThreadSafe)
 * 
 * @author william.liangf
 */
@SPI("javassist")
public interface ProxyFactory {
  • 生成动态代理的实现类
    1、jdk动态代理
    2、javassist



    消费端远程调用dubbo接口,客户端执行类实际上是其代理类。
    dubbo 为这一类公共rpc接口客户端生成动态代理,契合面向切面编程的思想

基于netty的rpc框架实现是国庆节的作业,大家应该都比较熟悉了,dubbo的consumer类似,这里细节不再铺开。


三克油

23种设计模式没有结束,同学们还需要继续努力添砖加瓦!

设计模式实践(一)-框架源码中的常见设计模式_第23张图片

你可能感兴趣的:(设计模式实践(一)-框架源码中的常见设计模式)