【设计模式】学习之结构型 适配器模式-装饰器模式-代理模式_tony_code_2017的博客-程序员资料

技术标签: Java的的  Java  设计模式  

本篇主要学习适配器模式,装饰器模式,代理模式的使用和结合开源组件源码进行分析,最后对他们作以比较

适配器模式

     适配器模式,提起适配器我们首先想到的就是春天框架中的RequestMappingHandlerAdapter,那么我们看看它的顶级接口的的HandlerAdapter的注释:

其中第一段的注释如下:

此接口用于允许{@link DispatcherServlet}无限期地
 *可扩展。{@ code DispatcherServlet}通过
 *此接口访问所有已安装的处理程序,这意味着它不包含特定于任何处理程序类型的代码。

该段注释的意思就是这个接口被用来允许前端控制器的DispatcherServlet的变成无限制性地可扩展的。通过这个接口前端控制器接受所有已经安装的处理器,这意味着前端控制器不包含编码指定到任何处理器类型。

第二段注释的意思是注意一个处理器可以是对象类型,这使得来自于其他框架的处理器不需要定制编码就能被集成到此框架中,也使得允许注解驱动的处理器对象,而这些对象没有遵守任何指定的的java的接口。

       以上两段注释可以理解为的的HandlerAdapter解决了处理程序的兼容性问题,因此我们可以使用控制器作为处理程序处理请求也可以使用的Servlet的作为处理程序处理请求,的详细实现可以参考的https://熔点.csdn.net / postedit / 84700384

适配器作为两个不兼容接口之间的桥梁,主要用在解决已经上线项目和新的环境中的接口不兼容的问题。例如在用SpringMVC框架诞生之前,我们是使用的Servlet开发的Web项目的,但是在春季项目发布后,我们想要将项目转移到用SpringMVC架构的时候,既面临着新需求的开发又面临着老项目的改造,这时候处理器就有两种,一种是弹簧提供的控制器一种是原有的Servlet中,而用SpringMVC就已经实现了这两种的处理:SimpleControllerHandlerAdapter和SimpleServletHandlerAdapter,然后根据的HandlerMapping返回的处理程序进行匹配,然后进行处理。

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

}

public class SimpleServletHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Servlet);
	}

	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		return null;
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		return -1;
	}

}

此种方式想较于依赖的方式更加灵活和可扩展,下面以飞虎例子演示依赖的方式实现适配器模式:

 鸟只能飞,虎只能跑,飞虎天空能飞地上能跑。

public interface Fly {
    /**
     * 空中能飞
     * @param region
     */
    void flying(String region);
}

public interface Run {
    /**
     * 地上能跑
     * @param region
     */
    void running(String region);
}

//鸟,空中能飞
public class Bird implements Fly{
    public void flying(String region) {
        if ("sky".equals(region)){
            System.out.println("i am bird ,i can fly in the sky.");
        }
    }
}

//虎,地上能跑
public class Tiger implements Run{
    public void running(String region) {
        if ("land".equals(region)){
            System.out.println("i am tiger,i can running");
        }
    }
}

//鸟的适配器类
public class BirdAdapter implements Fly{
    private Run run;

    public BirdAdapter(Run run) {
        this.run = run;
    }


    public void flying(String region) {
        if("land".equals(region)){
            run.running(region);
        }
    }
}

//飞虎类
public class FlyTiger implements Fly{

    BirdAdapter birdAdapter;

    public FlyTiger() {
        this.birdAdapter = new BirdAdapter(new Tiger());
    }

    public void flying(String region) {
        if ("sky".equals(region)){
            System.out.println("i am bird ,i can fly in the sky.");
        }else if("land".equals(region)){
            birdAdapter.flying(region);
        }else{
            System.out.println("无能为力");
        }
    }
}
//演示demo
public class AdapterDemo {

    public static void main(String[] args) {
        //一般的鸟只能飞
        Fly bird = new Bird();
        bird.flying("sky");
        //飞虎的鸟能飞能跑
        FlyTiger flyTiger = new FlyTiger();
        flyTiger.flying("sky");
        flyTiger.flying("land");
    }

}
//运行结果,飞虎天上可飞,地上可跑
i am bird ,i can fly in the sky.
i am bird ,i can fly in the sky.
i am tiger,i can running

以上是一般的适配器模式使用方式,但是SpringMVC中的因为要适配多种处理器并且还支持扩展,所以它是以接接的形式对外提供,每一个HandlerAdapter的实现类都代表了支持的一种处理器,

在每一个最终的实现类的支持方法中可以看到支持的类型:

装饰器模式:

       装饰器模式,顾名思义就是在不改变原有的事物时对其的功能进行一些修饰或者叫做扩展,例如女生化妆就是对自己装饰,那么女生没变还是那个女生,但是女生对外展示的形象变了。我最了解的装饰器模式的使用开源组件就是MyBatis的,其中的二级缓存实现就是使用到了该模式,下面我们就分析下的MyBatis(3.4.6版本)的二级缓存和装饰器模式:

1.被装饰的类:PerpetualCache

    该类是缓存接口的唯一真正实现类,其余实现缓存接口的类都是该类的装饰器类。该类以Mapper.xml的命名空间为ID,将该mapperXML文件对应的查询语句结果进行缓存,相关的缓存分析可见文章:https://mp.csdn.net/postedit/84321537 。

2.PerpetualCache的装饰器类之一:LoggingCache

    当我们进行缓存之后我们想要知道每次查询后缓存的命中率是多少,MyBatis的源码中有一个类LoggingCache实现了该功能,实现的原理就是使用装饰器模式,在LoggingCache中有两个类变量,命中和请求,初始值都为0。

LoggingCache-> SerializedCache-> LruCache-> PerpetualCache进行装饰,LoggingCache,SerializedCache,LruCache,PerpetualCache依次又对前者进行了修饰,每次从前者中取出缓存的结果前请求++,取出后对取出的结果进行判断是否为空,不为空则命中++,如果日志是可调试时输出命中/请求的值。该值就是我们需要知道的命中率。这种实现的好处就是在不改变原有类的前提下,实现了原有功能的扩展。假如使用继承,则会很臃肿,因为我们不仅要知道命中率,我们还有其他的关于缓存的需求(例如序列化,缓存各种策略等),如果使用继承,则最终的子类会有很多的方法,而且子类也不够灵活。

3.选择何种方式?

那么我们什么时候使用装饰器模式什么时候使用继承?还是看需求。首先,如果实现需求子类的继承层级多达四五层以上,那么建议可以考虑使用装饰器模式,因为继承达到一定层级子类会很膨胀;其次,如果需求变化比较多,需要扩展功能的方式灵活多变,那么我们就需要使用装饰器模式,因此MyBatis的支持自定义的缓存策略,所以装饰器模式在此处要比继承更好。

4.怎么实现装饰器模式

4.1抽象出一个接口

   缓存接口

   

public interface Cache {

  /**
   * @return The identifier of this cache
   */
  String getId();

  /**
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**
   * As of 3.3.0 this method is only called during a rollback 
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that 
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null 
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be 
   * available instead of hitting the database.
   *
   * 
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance
   */  
  void clear();

  /**
   * Optional. This method is not called by the core.
   * 
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();
  
  /** 
   * Optional. As of 3.2.6 this method is no longer called by the core.
   *  
   * Any locking needed by the cache must be provided internally by the cache provider.
   * 
   * @return A ReadWriteLock 
   */
  ReadWriteLock getReadWriteLock();

}

4.2修饰类和被修饰类都实现该接口

    修饰类中以接口的类型引入变量指向被修饰类

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

4.3修饰类的方法实现中调用被修饰类的引用方法,并添加扩展的功能实现。

 @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

代理模式

       提到代理模式,现实生活中比较常见,比如火车票代售窗口,销售某种商品的代理商等等,我们不用直接去火车站或者产品生成商就能直接购买,在实际开发中该模式多适用于控制访问权限的场景。

    卖票接口:

public interface SellTicket {
    void sell(String id);
}

    火车站实现类:

/**
 * 火车站卖票
 */
public class TrainStation implements SellTicket{
    public void sell(String id) {
        System.out.println("sell one ticket to " + id);
    }
}

  火车站代售点:

/**
 * 代售点卖票
 */
public class ProxyTicketAgent implements SellTicket{

    private TrainStation trainStation;

    public ProxyTicketAgent(TrainStation trainStation) {
        this.trainStation = trainStation;
    }

    public void sell(String id) {
        trainStation.sell(id);
    }
}

 演示演示:

public class ProxyDemo {
    public static void main(String[] args) {
        //通过代售点我们就可以买票了
        SellTicket sellTicket = new ProxyTicketAgent(new TrainStation());
        sellTicket.sell("123");
    }
}

演示执行结果:

sell one ticket to 123

菜鸟教程中关于这三者之间的区别描述如下:

和适配器模式的区别:适配器模式主要改变所考虑对象的接口。而且装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

       以上描述的代理模式属于静态代理,实际开发中使用较多的是动态代理,也就是我们在运行时才知道要代理的对象。动态代理分为JDK自带动态代理,CGLIB和动态代理,前者对于代理的类必须是实现了接口的,后者对于代理的类是不需要实现接口的。两者实现的技术上也不相同,前者是使用代理类和InvocationHandler的接口就可实现,后者是对要代理的类生成一个子类,可以查看相关文章详细了解该实现。弹簧AOP就是使用动态代理实现的,而Spring事务和拦截器等都是使用AOP实现的,也就说事务和拦截器也都是使用了代理模式。

JDK动态代理示例:

public class JDKProxyAgent implements InvocationHandler{

    private SellTicket target;

    public JDKProxyAgent(SellTicket target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行代理方法");
        return method.invoke(target,args);
    }
}

演示类:

public class ProxyDemo {
    public static void main(String[] args) {
        //通过代售点我们就可以买票了
//        SellTicket sellTicket = new ProxyTicketAgent(new TrainStation());
//        sellTicket.sell("123");
        //JDK动态代理演示
        System.out.println("*******************JDK动态代理演示****************************");
        SellTicket trainStation = new TrainStation();
        JDKProxyAgent jdkProxyAgent = new JDKProxyAgent(trainStation);
        SellTicket JDKProxy = (SellTicket) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
                trainStation.getClass().getInterfaces(),jdkProxyAgent);
        JDKProxy.sell("456");
    }
}

执行结果如下:

******************* JDK动态代理演示*************************** *
执行代理方法
卖一张票到456

CGLIB动态代理实现示例:

在项目中引入坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

被代理类:

public class BreadStore {
    public String hello(){
        return "hello";
    }
}

代理处理:

public class CglibProxy implements MethodInterceptor{
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行CglibProxy方法");
        return method.invoke(target,objects);
    }
}

演示演示:

public class ProxyDemo {
    public static void main(String[] args) {
        //通过代售点我们就可以买票了
//        SellTicket sellTicket = new ProxyTicketAgent(new TrainStation());
//        sellTicket.sell("123");
        //JDK动态代理演示
        System.out.println("*******************Cglib动态代理演示****************************");
        BreadStore breadStore = new BreadStore();
//        JDKProxyAgent jdkProxyAgent = new JDKProxyAgent(trainStation);
//        SellTicket JDKProxy = (SellTicket) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
//                trainStation.getClass().getInterfaces(),jdkProxyAgent);
//        JDKProxy.sell("456");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(breadStore.getClass());
        enhancer.setCallback(new CglibProxy(breadStore));
        BreadStore CglibProxy = (BreadStore) enhancer.create();
        System.out.println(CglibProxy.hello());
    }
}

 但是CGLIB对于那些类中用私人权限修饰符修饰的方法,是不能代理的。

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tony_java_2017/article/details/84616402

智能推荐

史上更全的MySQL高性能优化实战总结!_mysql高级性能优化及实战精讲_qq_4278923的博客-程序员资料

一、前言MySQL对于很多Linux从业者而言,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰。在进行MySQL的优化之前必须要了解的就是MySQL的查询过程,很多的查询优化工作实际上就是遵循一些原则让MySQL的优化器能够按照预想的合理方式运行而已。今天给大家体验MySQL的优化实战,助你高薪之路顺畅!二、优化的哲学注意:优化有风险,涉足需谨慎!2.1、优化可能带来的问题 优化不总是对一个单纯的环境进行,还很可能是一个复杂的已投产的系统。 ..

UE4创建VR项目笔记_用vrpawn制作游戏_尔德顶顶顶顶的博客-程序员资料

VR手动创建项目1,建立VR测试时,最好选择最低设置,保持流畅的性能,2,创建角色(Pawn)和游戏模式(GameMode)3,将游戏模式细节面板上Classes中的默认Pawn类改为创建的VRPawn类,4,Pawn角色蓝图类中添加摄像机和运动控制器,1)添加一个Scene改为CameraRoot,再子集添加摄像机,2)添加两个motioncontroller,分别控制左右,在设...

用树莓派打造世界上最小的“iMac”_程序猿DD_的博客-程序员资料

点击上方蓝色“程序猿DD”,选择“设为星标”回复“资源”获取独家整理的学习资料!作者 |xplanet来源 |https://www.oschina.net/news/118215/...

python计算图片的相似度_python特征相似度_nejssd的博客-程序员资料

python计算图片的相似度计算方法完整代码测试这里需要用到PIL,如果没有安装PIL,需要先pip install PIL计算方法一、将图片缩放为10×10(缩放比例因图片大小而异)二、读取每一点灰度化后的像素三、计算每一行的像素平均值四、生成特征序列。 把每一点的像素与所在行的像素平均值作比较,如果大于像素平均值,则特征序列+‘1’,反之+‘0’。最后得到的特征序列是由1和0组成的...

Sql去重语句_夜听梧桐雨,的博客-程序员资料

海量数据(百万以上),其中有些全部字段都相同,有些部分字段相同,怎样高效去除重复?如果要删除手机(mobilePhone),电话(officePhone),邮件(email)同时都相同的数据,以前一直使用这条语句进行去重:delete from 表 where id not in(select max(id) from 表 group by mobilePhone,officePhone,email )ordelete from 表 where id not in(select min(id)

Android Studio进阶之路(2) 导入项目时layout页面的dependencies的相关设置_whwh1233的博客-程序员资料

目前的Android studio导入v4包最坑的点因为Android的API设定,在Android Studio3.4版本之后,google目前大力支持使用Androidx API进行开发,默认新建project时都会勾选此选项,而Androidx与之前的v4、v7包都不兼容,所有会出现导入v4包无反应的情况,只需在新建项目是取消勾选使用androidx即可。dependencies定义一...

随便推点

SDS:一个简易动态字符串库_sds github_吉阿的博客-程序员资料

http://blog.jobbole.com/68119/英文原文: https://github.com/antirez/sds/blob/master/README.mdSDS(Simple Dynamic Strings)是一个C语言字符串库,设计中增加了从堆上分配内存的字符串,来扩充有限的libc字符处理的功能,使得:使用更简便二进制安全计算更有效率而且仍旧…

html如何设置字体纵向居中,html如何实现文本上下居中_weixin_39860946的博客-程序员资料

html实现文本上下居中的方法:首先创建一个HTML示例文件;然后创建一个文本框;接着定义Text的height属性;最后通过css中“vertical-align:middle;”等属性实现文本上下居中即可。本教程操作环境:Windows7系统、HTML5&amp;&amp;CSS3版,DELL G3电脑。推荐:css视频教程让HTML中的文本框中的文字垂直居中当你自己定义了 Text 的 he...

Ubuntu SVN 客户端_「已注销」的博客-程序员资料

Ubuntu SVN 客户端 安装 svn客户端:apt-get install subversion,然后根据提示一步一步,就完成了 svn的安装。当然,也可以源码安装 svn,下载 subversion 一个最新版本的源码包,解压之后就可以安装了。2、 新建一个目录,cd 到新建目录下,将文件 checkout 到本地目录...

传统的ARM与Cortex-M3_孔雀东南飞的博客-程序员资料

要使用低成本的 32位处理器,开发人员面临两种选择,基于Cortex-M3内核或者ARM7TDMI内核的处理器。如何做出选择?选择标准又是什么?本文主要介绍了ARM Cortex-M3内核微控制器区别于ARM7的一些特点,帮助您快速选择。1.ARM实现方法    ARM Cortex-M3是一种基于ARM7v架构的最新ARM嵌入式内核,它采用哈佛结构,使用分离的指令和数据总线(冯诺伊曼结构下,数据和指令共用一条总线)。从本质上来说,哈佛结构在物理上更为复杂,但是处理速度明显加快。根据摩尔定理,复杂性并不是一

又一电商杀手锏——区块链应用--交易印_honglinhai的博客-程序员资料

开启交易印功能,增加交易信任度越来越多用户通过爱用智能网站搭建起自营电商,在享受更宽广的私域流量同时,用户直接下单,款项直达商户也着实为商家带来更好的交易体验。但如此方便高效的自建站交易方式也有信任度不足的问题。毕竟相比电商平台,以商家个体信用作为背书的自营电商要逊色不少。针对这一痛点,爱用建站平台全新推出区块链应用--交易印。相比传统“平台担保”的中间模式,交易印实现无需保证押金,无需延长账期的交易增信新方式,真正实现“0成本”开启自营电商。什么是交易印?交易印功能是基于区块链..

Common Control - The VB Way (1)_DevilXelloss的博客-程序员资料

Common Control: The VB Way!  “救救我!为什么我做的小程序用VB安装向导打包后变得那么大?”我们几乎可以在所有的VB论坛上看到这样的帖子。这个糟糕的结果源自VB方便的开发方式:一些VB程序员几乎毫不节制地使用ActiveX控件,而安装向导只是一个没有知觉的程序,它把那些ActiveX控件全部加入发行包——于是你就会跑到网上发帖子求救。  怎么办?简单的方法是

推荐文章

热门文章

相关标签