【设计模式】学习之结构型 适配器模式-装饰器模式-代理模式

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

适配器模式

     适配器模式,提起适配器我们首先想到的就是春天框架中的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对于那些类中用私人权限修饰符修饰的方法,是不能代理的。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值