鸣谢:《大话设计模式》

简介

适配器这个词应该是最早出现在电工学里。
在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式通过多重继承对一个接口与另一个接口进行匹配,而Java、C#、VB.NET等语言都不支持多重继承(C++支持),也就是
一个类只有一个父类,所以我们这里主要讲的是对象适配器。”

是一种结构型设计模式,用于将一个接口转换成客户端所期望的另一个接口,使不兼容的接口能够协同工作。适配器模式允许两个不兼容的接口能够协同工作,而不需要修改现有的代码。

主要组成部分:

  • 目标接口(Target):目标接口是客户端所期望的接口,它定义了客户端使用的方法。

  • 适配器(Adapter):适配器是一个实现目标接口的类,它包装了一个被适配的对象,并通过调用该对象的方法来实现目标接口的方法。适配器负责将不兼容的接口转换成兼容的接口。

  • 被适配者(Adaptee):被适配者是需要被适配的对象,它具有客户端所期望的方法,但其接口与目标接口不兼容。

工作流程

  • 客户端需要与目标接口交互,但目前只有一个被适配者对象,其接口不兼容目标接口。

  • 适配器类被创建,并实现了目标接口。

  • 适配器类包装了被适配者对象,并通过调用被适配者对象的方法来实现目标接口的方法。

  • 现在客户端可以直接使用适配器对象来与被适配者对象进行交互,而不需要修改客户端代码或被适配者对象的代码。


类版被适配者例子——对象适配器模式

推荐 组合的方式

结构


代码

目标接口(Target):

/**
 * @author: lpy
 * @Date: 2023/10/27
 * @desc: Target目标接口
 */
public interface ChinesePlug {
    /**
     * 中国插头
     */
    void insertChinesePlug();
}

适配器(Adapter):

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class SocketAdapter implements ChinesePlug{
    private ForeignSocket foreignSocket;

    public SocketAdapter(ForeignSocket foreignSocket){
        this.foreignSocket = foreignSocket;
    }

    @Override
    public void insertChinesePlug(){
        System.out.println("[-*-]:正使用适配器链接,将中国插头插入外国插座");
        foreignSocket.insertForeignSocket();
    }
}

被适配者(Adaptee)

/**
 * @author: lpy
 * @Date: 2023/10/27
 * @desc: Adaptee 被适配者
 */
public class ForeignSocket {
    void insertForeignSocket(){
        System.out.println("外国插座");
    }
}

测试类

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class Client {
    public static void main(String[] args) {
        ForeignSocket foreignSocket = new ForeignSocket();
        SocketAdapter socketAdapter = new SocketAdapter(foreignSocket);

        socketAdapter.insertChinesePlug();
    }
}

类版被适配者例子——类适配器模式

目标接口(Target):

/**
 * @author: lpy
 * @Date: 2023/10/27
 * @desc: Target目标接口
 */
public interface ChinesePlug {
    /**
     * 中国插头
     */
    void insertChinesePlug();
}
/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class ChineseNormalComputerPulgin implements ChinesePlug{
    @Override
    public void insertChinesePlug() {
        System.out.println("中国普通电脑插头");
    }
}

适配器(Adapter):

这里强调通过继承的方式

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class SocketAdapter extends ForeignSocket implements ChinesePlug {

    @Override
    public void insertChinesePlug(){
        System.out.println("[-*-]:正使用适配器链接,将中国插头插入外国插座");
        // 这里强调的是通过继承
        super.insertForeignSocket();
    }
}

被适配者(Adaptee)

/**
 * @author: lpy
 * @Date: 2023/10/27
 * @desc: Adaptee 被适配者
 */
public class ForeignSocket {
    void insertForeignSocket(){
        System.out.println("外国插座");
    }
}

测试类

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class Client {
    public static void main(String[] args) {
        SocketAdapter socketAdapter = new SocketAdapter();

        socketAdapter.insertChinesePlug();
    }
}

接口版被适配者例子

结构


代码

目标接口(Target):

/**
 * @author: lpy
 * @Date: 2023/10/27
 * @desc: 目标接口
 */
public interface ChinesePlug {
    void insertChinesePlug();
}

适配器(Adapter):

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class Adapter implements ChinesePlug{
    private ForeignSocket foreignSocket;

    public Adapter(ForeignSocket f){
        this.foreignSocket = f;
    }


    @Override
    public void insertChinesePlug() {
        System.out.println("【~~】通过适配器将中国插头插入外国插座");
        this.foreignSocket.insertForeignSocket();
    }
}

被适配者(Adaptee)

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public interface ForeignSocket {
    /**
     * 外国插座,与中国插头不兼容
     */
    void insertForeignSocket();
}
/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class ForeignSocketImpl implements ForeignSocket{
    @Override
    public void insertForeignSocket() {
        System.out.println("插入外国的大插座~~");
    }
}

客户端测试类

/**
 * @author: lpy
 * @Date: 2023/10/27
 */
public class InterfaceAdapterTest {
    public static void main(String[] args) {
        /**
         * 在这个例子中,ForeignSocketImpl实现了ForeignSocket接口,
         * Adapter适配器类仍然实现了中国插头接口,同时包装了外国插座接口。
         * 这使小明可以连接中国的插头到外国的插座,即使外国插座是一个接口。适配器模式的核心思想是将不兼容的接口适配在一起,无论这些接口是类还是接口。
         */
        ForeignSocket foreignSocket = new ForeignSocketImpl();
        Adapter adapter = new Adapter(foreignSocket);

        adapter.insertChinesePlug();
    }
}

应用

  • 当需要使用一个已存在的类,但其接口与所需接口不兼容时。

  • 当希望创建一个可以复用的类,可以将其用于不同的场景,以便与多个接口进行交互。

  • 当需要解耦客户端和具体的被适配者类。

DispatcherServlet 中的doDispatch充当了上帝视角,也就是我们自己写的例子中的client测试类的角色。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ....                
    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ....    
}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        // 会遍历所有的adapters,返回支持的adapter        
        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");
}
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    long getLastModified(HttpServletRequest request, Object handler);
}

org.springframework.web.servlet.mvc;包下的Controller接口是被适配的,这个Controller有很多实现类,调用方式就不是确定的,如果直接调用Controller的方法,
就要判断是哪个实现类,就要多很多if else,spring提供了HandlerAdapter适配器接口,他的实现类都是为了 Controller的实现类都能找到对应的适配器,让适配器执行相应
的方法。
HandlerAdapter将不同类型的Controller与MVC框架进行适配,使它们能够协同工作。在Spring MVC中,有多个HandlerAdapter实现,如
SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter等,每个适配器负责将不同类型的Controller适配到框架中。

例子来源于慕课视频《Java设计模式精讲 Debug方式+内存分析》

详情看文章1springmvc适配器模式—HandlerAdapter

适配器模式优缺点

  • 优点:
    • 解耦合:适配器模式允许客户端与不兼容的接口进行交互,从而解耦了客户端和被适配者。

    • 复用性:适配器模式可以重复使用已有的类,无需修改其源代码,只需要创建适配器类。

    • 扩展性:可以轻松添加新的适配器,以便与不同的接口进行交互,而无需修改现有代码。

  • 缺点:

    • 复杂性增加:引入适配器可能会增加代码的复杂性,因为需要创建适配器类并管理适配逻辑。

    • 性能开销:适配器模式可能引入一定的性能开销,因为每次调用适配器方法都会经过一次转换。

    • 可读性降低:由于内部调用原有的接口,可读性降低

7 大设计原则

不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;

- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。

- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。

- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。

- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。

- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。

- 迪米特法则(Law of Demeter - LoD):
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。

源码位置

全部源码github.com/xs-alpha/designPattern

参考文献

如有内容侵权,麻烦联系博主删除

浙公网安备33011302000604

辽ICP备20003309号