设计模式(Design_Pattern).png

1. 引入

适配器模式的思想本身就来源于生活,我们在日常生活中不时地就会用到它。例如:如果不同电压之间需要电源适配器的转换才能使用;不同语言的人之间交流需要一个懂这两门语言的翻译;不同货币之间的相关兑换需要银行这样的兑换机构……因此,使用适配器并不会改变双方本身的状态,而是通过一个中间的桥梁,实现彼此的转换,使得某一方可以通过桥梁正常的使用另一方,或是彼此之间相互正常使用。

2.定义

适配器模式的目的是将一个类的接口转换为客户希望使用的另一个接口,使得原本由于接口不兼容而不能在一起工作的那些类能一起工作。对照于生活中的例子,我们不难理解。常用的适配器模式有两种形式:

  • 类适配器模式
  • 对象适配器模式

另外,根据实际的需求可将适配器模式扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口。

适配器模式种通常包含三个角色:
适配器模式角色.png

  • 目标抽象类(Target):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类
  • 适配器类(Adapter):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系
  • 适配者类(Adaptee):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码

3. 类适配器模式

下面关于几种模式的介绍,我们都以货币兑换的例子进行说明。

类适配器模式.png

首先我们有一个表示美金的类Dollar,通过它可以得到固定数额的美金:

  1. public class Dollar {
  2. public int getDollar(){
  3. int number = 100;
  4. System.out.println("I have $100...");
  5. return number;
  6. }
  7. }

而现在我们希望换成人民币,就需要定义表示人民币的接口RMB:

  1. public interface RMB {
  2. public int getRMB(int dollar);
  3. }

如果想要实现美金到人民币的兑换,我们就需要一个兑换的机构,即模式名中的适配器,这里定义为Bank,它不仅继承了Dollar,而且实现了RMB接口中的getRMB()

  1. public class Bank extends Dollar implements RMB {
  2. @Override
  3. public int getRMB(int dollar) {
  4. return dollar * 7;
  5. }
  6. }

getRMB()实现了将给定数额的美金兑换为相应数额人民币的功能。当用户想兑换时,他只需要调用Bank实例化对象的getRMB()方法即可。

  1. public class Person {
  2. public static void main(String[] args) {
  3. Bank bank = new Bank();
  4. int rmb = bank.getRMB(new Dollar().getDollar());
  5. System.out.println("I could get " + "¥" + rmb + "...");
  6. }
  7. }

输出为:

  1. I have $100...
  2. I could get 700...

4. 对象适配器模式

对象适配器模式.png
根据设计模式的原则和思想,程序中应尽量避免使用继承,而应该更多的使用聚合和组合。因此,我们可以使用聚合将上面的例子转换为对象适配器模式的形式。首先,同样是创建Dollar类,这里我们做了些更新:

  1. public class Dollar {
  2. private int money;
  3. public Dollar(int money) {
  4. this.money = money;
  5. }
  6. public Dollar() {
  7. }
  8. public int getDollar(){
  9. System.out.println("I have $" + this.money + "...");
  10. return this.money;
  11. }
  12. }

RMB 接口依然使用和上面同样的形式:

  1. public interface RMB {
  2. public int getRMB();
  3. }

为了使用聚合来代替继承,我们需要将Dollar类对象作为RMB的变量进行使用,并通过构造函数来初始化它。

  1. public class Bank implements RMB{
  2. private Dollar dollar;
  3. public Bank(Dollar dollar) {
  4. super();
  5. this.dollar = dollar;
  6. }
  7. @Override
  8. public int getRMB(){
  9. if (this.dollar != null){
  10. return this.dollar.getDollar() * 7;
  11. }
  12. System.out.println("please give your money...");
  13. return 0;
  14. }
  15. }

当用户在使用时实例化Bank对象时需要传入Dollar类对象,同时在Dollar类对象中传入想要兑换的美金数额。

  1. public class Person {
  2. public static void main(String[] args) {
  3. Bank bank = new Bank(new Dollar(100));
  4. int rmb = bank.getRMB();
  5. System.out.println("I could get " + "¥" + rmb + "...");
  6. }
  7. }

5. 双向适配器模式

双向适配器模式.png

银行不应该只有美金兑换人民币的业务,同时它应该由人民币兑换美金的业务。因此,为了实现美金和人民币之间的相互兑换就需要使用双向适配器模式。

首先定义接口Dollar和接口RMB,这里为了更好的表义,将其命名为了Dollar2RMB和RMB2Dollar:

  1. public interface Dollar2RMB {
  2. int getRMB(int dollar);
  3. }
  1. public interface RMB2Dollar {
  2. int getDollar(int rmb);
  3. }

此时,Bank应该同时实现这两个接口,并重写接口中的方法:

  1. public class Bank implements RMB2Dollar, Dollar2RMB{
  2. @Override
  3. public int getRMB(int dollar) {
  4. return dollar * 7;
  5. }
  6. @Override
  7. public int getDollar(int rmb) {
  8. return rmb / 7;
  9. }
  10. }

用户通过银行就可以实现两种货币之间的兑换了。

  1. public class Person {
  2. public static void main(String[] args) {
  3. Bank bank = new Bank();
  4. int dollar = bank.getDollar(700);
  5. System.out.println("I have ¥700 and I could change $" + dollar);
  6. System.out.println("I have $100 and I could change ¥" + bank.getRMB(100));
  7. }
  8. }

输出为:

  1. I have 700 and I could change $100
  2. I have $100 and I could change 700

6.总结

最后总结一下适配器模式的优缺点,它的优点有:

  • 客户端通过适配器可以透明地调用目标接口
  • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题

缺点在于更换适配器较为麻烦,我们要充分的考虑两端的情况,所以慎用。