# 适配器模式

# 概念

适配器模式( Adapter Pattern )是一种结构型设计模式,用于将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。它主要分为类适配器和对象适配器两种实现方式。

# 作用

  1. 提高兼容性:使原本接口不兼容的类能够一起工作。

  2. 增强复用性:可以复用现有类的功能,而无需修改其源代码。

  3. 增加灵活性:通过适配器模式,可以方便地更换适配器或适配的类,提高系统的灵活性。

  4. 解耦设计:将接口转换逻辑封装在适配器中。

# 场景

  1. 系统扩展:当需要将新功能集成到现有系统中,但新功能的接口与现有系统不兼容时。

  2. 数据格式转换:当需要将一种数据格式转换为另一种数据格式时。

  3. 接口适配:当需要使用某个类的功能,但其接口与当前系统不匹配时。

  4. 第三方库或服务集成:当需要集成第三方库或服务,但其接口与系统不兼容时。

# 举例

# 类适配器


通过继承被适配的类并实现目标接口来实现适配。

package net.feixiang.structural.adapter;

/**
 * 目标接口,定义了一个请求方法。
 * 适配器模式中的目标接口,客户端通过这个接口与适配器交互。
 */
public interface Target {
    /**
     * 请求方法
     */
    void request();
}
package net.feixiang.structural.adapter;

/**
 * 被适配的类
 * 在适配器模式中,这个类是需要被适配的类,它有一个特殊的请求方法。
 */
public class Adaptee {
    /**
     * 特殊请求方法
     */
    public void specificRequest() {
        System.out.println("被适配者的特殊请求方法。");
    }
}

package net.feixiang.structural.adapter;

/**
 * 类适配器
 * 通过继承被适配者类,并实现目标接口,将被适配者的特殊请求方法适配为目标接口的请求方法。
 */
public class ClassAdapter extends Adaptee implements Target {
    /**
     * 重写请求方法,将被适配者的特殊请求方法适配为目标接口的请求方法。
     */
    @Override
    public void request() {
        // 调用被适配者的特殊请求方法
        specificRequest();
    }
}

运行示例:

package net.feixiang.structural.adapter;

/**
 * 适配器模式演示
 * 通过类适配器将被适配者的特殊请求方法适配为目标接口的请求方法。
 */
public class AdapterDemo {
    public static void main(String[] args) {
        Target target = new ClassAdapter();
        target.request();
    }
}

控制台输出:

被适配者的特殊请求方法。

# 反例


如果没有适配器模式,会导致以下问题:

  1. 兼容性问题导致无法协作

    在没有适配器模式的情况下,当系统中存在接口不兼容的类时,这些类将无法直接一起工作。例如在上述例子中,如果客户端代码期望调用 Target 接口的 request() 方法,而被适配的类 Adaptee 提供的是 specificRequest() 方法,由于接口不匹配,客户端代码无法直接调用 Adaptee 的方法,也就无法利用 Adaptee 类的功能。

  2. 代码复用困难

    现有类可能具有我们需要的功能,但由于接口不兼容,我们不能直接复用这些类的功能。若要使用这些功能,可能需要对现有类的源代码进行修改,使其接口符合我们的需求。然而,修改现有类的源代码可能会引入新的问题,比如破坏原有的功能、增加代码的复杂度,并且可能违反开闭原则(对扩展开放,对修改关闭)。

  3. 系统扩展性差

    当需要将新功能集成到现有系统中,但新功能的接口与现有系统不兼容时,如果没有适配器模式,就需要对现有系统进行大规模的修改,以适应新功能的接口。这不仅增加了开发的难度和工作量,还可能导致现有系统的稳定性受到影响。

如果不使用适配器模式,可以通过以下两种方式实现,均非最佳方案

  1. 手动适配

    可以通过手动编写代码来调用被适配类的方法,然后在客户端代码中进行相应的处理,以满足目标接口的要求。例如,在上述例子中,可以不使用 ClassAdapter ,而是在客户端代码中直接创建 Adaptee 对象并调用其 specificRequest() 方法。但这种方式破坏了客户端代码与目标接口的约定,使得客户端代码需要了解被适配类的具体实现细节,降低了代码的可维护性和可扩展性。

    package net.feixiang.structural.adapter.contrary;
    
    import net.feixiang.structural.adapter.Adaptee;
    
    /**
     * 手动适配器演示
     * 通过手动调用被适配者的特殊请求方法来适配目标接口的请求方法。
     */
    public class ManualAdapterDemo {
        public static void main(String[] args) {
            // Target的实现类最初的请求方法
            // request();
            
            Adaptee adaptee = new Adaptee();
    
            // 很明显这里需要修改客户端代码,因为原来的请求方法是 request()
            adaptee.specificRequest();
        }
    }
    
  2. 修改现有类

    可以直接修改 Adaptee 类,使其实现 Target 接口。但这种方式违反了开闭原则,因为我们对现有类的源代码进行了修改,可能会引入新的问题,并且如果 Adaptee 类是第三方库提供的,我们可能无法修改其源代码。

    package net.feixiang.structural.adapter.contrary;
    
    import net.feixiang.structural.adapter.Target;
    
    /**
     * 修改被适配者类
     * 不推荐,违反开闭原则,且可能无法修改第三方类。
     */
    public class ModifiedAdaptee implements Target {
        @Override
        public void request() {
            specificRequest();
        }
    
        public void specificRequest() {
            System.out.println("被适配者的特殊请求方法。");
        }
    }
    

# 对象适配器


通过在适配器类中包含被适配类的实例并实现目标接口来实现适配。

还是示例中,ClassAdapter 通过继承 Adaptee 类来实现适配,而 ObjectAdapter 通过包含 Adaptee 类的实例来实现适配:

package net.feixiang.structural.adapter;

/**
 * 对象适配器
 * 通过组合被适配者类,并实现目标接口,将被适配者的特殊请求方法适配为目标接口的请求方法。
 */
public class ObjectAdapter implements Target {
    private Adaptee adaptee;

    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.specificRequest(); // 调用被适配类的方法
    }
}
package net.feixiang.structural.adapter;

/**
 * 对象适配器
 * 通过组合被适配者,将被适配者的特殊请求方法适配为目标接口的请求方法。
 */
public class ObjectAdapterDemo {
    public static void main(String[] args) {
        // 创建被适配者实例
        Adaptee adaptee = new Adaptee();
        // 创建适配器实例,将被适配者传入适配器
        Target target = new ObjectAdapter(adaptee);
        // 调用目标接口的方法
        target.request();
    }
}

# 解析


ObjectAdapter 类实现了 Target 接口,并且在构造函数中接收一个 Adaptee 类的实例,将其保存为私有成员变量。在 ObjectAdapter 类的 request() 方法中,调用了被适配类 AdapteespecificRequest() 方法,符合开闭原则。

同样,如果没有对象适配器,也要使用类似类适配器中的反例来实现相应的效果,导致兼容性问题。

# 特点

# 优点


  1. 提高兼容性:通过适配器模式,可以将原本不兼容的接口转换为目标接口,使得不同接口的类能够协同工作。

  2. 增强复用性:可以复用已有的类的功能,而无需修改其源代码,符合开闭原则。

  3. 增加灵活性:适配器模式提供了灵活的适配方式,可以通过类适配器或对象适配器来实现适配。

  4. 代码可维护性:将适配逻辑封装在适配器类中,使得代码结构更加清晰,便于维护和扩展。

# 缺点


  1. 增加复杂度:适配器模式会增加系统的类或对象的数量,使得系统更加复杂。

  2. 性能开销:在某些情况下,适配器模式可能会引入一定的性能开销,尤其是在对象适配器中,需要维护被适配类的实例。

  3. 适配器实现复杂度:适配器的实现可能会比较复杂,尤其是在需要适配多个接口或处理复杂的适配逻辑时。


总结


适配器模式是结构型设计模式,作用是将一个类的接口转换成客户期望的另一个接口,让原本接口不兼容的类能一起工作,还能提高复用性、灵活性,解耦设计。实现方式有类适配器(继承被适配类并实现目标接口)和对象适配器(包含被适配类实例并实现目标接口)。



微信公众号

QQ交流群
原创网站开发,偏差难以避免。

如若发现错误,诚心感谢反馈。

愿你倾心相念,愿你学有所成。

愿你朝华相顾,愿你前程似锦。