zoukankan      html  css  js  c++  java
  • 设计模式-适配器模式

    定义:

        适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

        在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

        根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

    主要优点:

        将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。

        增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。

        灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

    具体来说,类适配器模式还有如下优点:

        由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

    对象适配器模式还有如下优点:

        一个对象适配器可以把多个不同的适配者适配到同一个目标;

        可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

    类适配器模式的缺点如下:

        对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;

    适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;

    在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

    对象适配器模式的缺点如下:

        与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

    角色:

    • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

    • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

    • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

    结构说明:

        

    类适配器实现:

    1、被适配的类

    public class Adaptee {
        public void adapteeRequest() {
            System.out.println("被适配者的方法");
        }
    }

    2、定义一个目标接口

    public interface Target {
        void request();
    }

    3、通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现

    public class Adapter extends Adaptee implements Target{
        @Override
        public void request() {
            //...一些操作...
            super.adapteeRequest();
            //...一些操作...
        }
    }

    4、调用测试、这样我们即可在新接口 Target 中适配旧的接口或类

    Target adapterTarget = new Adapter();
    adapterTarget.request();

    对象适配器实现:    

    public class Adapter implements Target{
        // 适配者是对象适配器的一个属性
        private Adaptee adaptee = new Adaptee();
    
        @Override
        public void request() {
            //...
            adaptee.adapteeRequest();
            //...
        }
    }

    应用场景:

    类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:

      (1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。

      (2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。

      以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。

    接口适配器使用场景:

      (1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。

    spring源码中使用场景:

        1、我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。我们以往在spring和hibernate框架中总是配置一个数据源,因而sessionFactory的dataSource属性总是指向这个数据源并且恒定不变,所有DAO在使用sessionFactory的时候都是通过这个数据源访问数据库。

        但是现在,由于项目的需要,我们的DAO在访问sessionFactory的时候都不得不在多个数据源中不断切换,问题就出现了:如何让sessionFactory在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能在spring的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢? 

        首先想到在spring的applicationContext中配置所有的dataSource。这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。然后sessionFactory根据客户的每次请求,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。

        spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。 

    2、在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。

        Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThreowSadvice的。

     每个类型Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。

        Spring需要将每个Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对Advice进行转换

    1)、MethodBeforeAdvice类:Adaptee

    public interface MethodBeforeAdvice extends BeforeAdvice {  
      void before(Method method, Object[] args, Object target) throws Throwable;
    }

    2)、Adapter类接口:Target 

    public interface AdvisorAdapter { 
      boolean supportsAdvice(Advice advice);  
      MethodInterceptor getInterceptor(Advisor advisor); 
    }

    3)、MethodBeforeAdviceAdapter类,Adapter 

    class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
      public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
      } 
      public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
      } 
    }

     

     

    参考原文链接:https://blog.csdn.net/wwwdc1012/article/details/82780560

  • 相关阅读:
    golang 使用 os/exec
    golang调用 exec命令 出现too many open files
    nginx: [error] invalid PID number “” in “/usr/local/var/run/nginx/nginx.pid”
    Docker 实时查看docker容器日志
    curl 查看HTTP 响应头信息
    go -- go 程序 启动docker容器
    go -- 测试
    Linux /var/log下各种日志文件
    LDAP -- ldap 的cn, ou, dc的含义
    一条命令 杀死某个端口 所有程序
  • 原文地址:https://www.cnblogs.com/yuarvin/p/13027144.html
Copyright © 2011-2022 走看看