设计模式之适配器模式

9 适配器模式

9.1 适配器简介

引入:

我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?

答案:引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容

在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。

概述:

与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adapter),即被适配的类。

适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这

个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。

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

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

适配器模式定义如下:

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

适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

9.2 类适配器模式

案例:

举个生活的小例子,手机充电能直接使用220V的电压嘛?肯定是不行的,因此就要在电源处上进行电压适配。

  • 需要适配的接口
// 适配接口
public interface IVoltage5V {
    public int output5V();
}
  • 需要充电的手机
public class Phone {
    // 充电方法
    public void charging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压5V,正常充电");
        } else if (iVoltage5V.output5V() > 5) {
            System.out.println("电压过高,不能充电");
        }
    }
}
  • 被适配的类
// 被适配的类
public class Voltage220V {
    public int output220V() {
        int src = 220;
        System.out.println("家庭电压:" + src + "伏");
        return src;
    }
}
  • 适配器类
// 适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
        int srcV = output220V();
        int dstV = srcV / 44;
        return dstV;
    }
}
  • 测试类
public class Client {
    public static void main(String[] args) {
        System.out.println("类适配器模式");
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter());
    }
}

测试结果:
    类适配器模式
    家庭电压:220伏
    电压5V,正常充电

分析:

  • Java 是单继承机制,所以类适配器需要继承 Voltage220V类这一点算是一个缺点, 因为这要求 IVoltage5V必须是接口,有一定局限性;
  • Voltage220V类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
  • 由于其继承了 Voltage220V类,所以它可以根据需求重写 Voltage220V类的方法,使得 Adapter 的灵活性增强了。

9.3 对象适配器模式

改进:

根据“ 合成复用原则”,在系统中尽量使用 关联关系(聚合)来替代继承关系

  • 适配接口
// 适配接口
public interface IVoltage5V {
    public int output5V();
}
  • 手机类
public class Phone {
    //充电
    public void charging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5V() == 5) {
            System.out.println("电压为 5V, 可以充电~~");
        } else if (iVoltage5V.output5V() > 5) {
            System.out.println("电压大于 5V, 不能充电~~");
        }
    }
}
  • 被适配的类
// 被适配的类
public class Voltage220V {
    //输出 220V 的电压,不变
    public int output220V() {
        int src = 220;
        System.out.println("电压=" + src + "伏");
        return src;
    }
}

  • 适配器类
// 适配器类
public class VoltageAdapter implements IVoltage5V {
    private Voltage220V voltage220V;

    // 通过构造器,传入一个 Voltage220V实例
    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5V() {
        int dst = 0;
        if (null != voltage220V) {
            int src = voltage220V.output220V();// 获取220V电压
            System.out.println("使用对象适配器,进行转换");
            dst = src / 44;
            System.out.println("适配完成,输出电压:" + dst);
        }
        return dst;
    }
}
  • 客户端测试
public class Client {
    public static void main(String[] args) {
        System.out.println(" === 对象适配器模式 ====");
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter(new Voltage220V()));
    }
}

测试结果:
     === 对象适配器模式 ====
    电压=220伏
    使用对象适配器,进行转换
    适配完成,输出电压:5
    电压为 5V, 可以充电~~

9.4 接口适配器模式

概述:

  • 一些书籍称为:接口适配器模式(DefaultAdapter Pattern)或却省适配器模式
  • 核心思路:当 不需要全部实现接口提供的方法时,可先 设计一个抽象类实现 接口,并为该接口中每个方法提供一个 默认实现(空方法),那么该 抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  • 适用于一个接口不想使用其所有的方法的情况。

接口适配器结构图:

  • ServiceInterface**(适配者接口)

它是一个接口,通常在该接口中声明了大量的方法。

  • AbstractServiceClass(缺省适配器类)

它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。

  • ConcreteServiceClass(具体业务类)

它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法也不得不提供空实现。

在有了缺省适配器之后,可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。

9.5 主要优缺点

1、主要优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
  • 类适配器模式还有如下优点:
  • 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强
  • 对象适配器模式还有如下优点:
  • 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

2、主要缺点

类适配器主要缺点:

  • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  • 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器主要缺点:

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

3、适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值