适配器和桥接模式

一、适配器模式

1.1 概述

适配器模式充当两个不兼容接口之间的桥梁,属于结构型模式,它通过中间件(适配器)将一个类的接口转换为客户期望的另一个接口。如生活中的充电器,生活用电的电压是220V但是手机的工作电压没那么高,于是就有了充电器(变压器)让手机能在220V电压下充电,在这电源充电器就充当了一个适配器的角色。

定义:将一个类的接口转换为客户希望的另一个接口,适配器让那些接口不兼容的类可以一起工作。

适配器模式其中包括了类适配器和对象适配器。在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器和适配者之间是继承(或实现)关系

1.2 模式结构

适配器模式包括对象适配器和类适配器,其中包括3个角色:

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

类适配器模式结构图:

image-20240623222808013

对象适配器模式结构图:

image-20240623222842575

1.3 实现与应用实例

类适配器模式:

    public class Adapter : Adaptee, Target
    {
        public void request()
        {
            this.specificRequest();
        }
    }

对象适配器模式:

    public class Adapter : Target
    {
        private Adaptee adaptee;

        public Adapter()
        {
            adaptee = new Adaptee();
        }

        public void request()
        {
            adaptee.specificRequest();
        }
    }

以国家之前的电压为例,中国电网的电压是以220V输出的,而美国电网的电压是以110V输出的,现在要将美国电压转接(适配)到中国使用。、

  • 类适配器:

    // 客户期望的接口: 220V电压输出
    public interface Target
    {
      public int chargeBy220V();
    }
    // 现有接口:只能通过110V电压充电
    public interface Adaptee
    {
      public int chargeBy110V();
    }
    // 现有接口的具体实现类,美国供电器:通过110V电压供电
    public class AmericanCharger : Adaptee
    {
      public int chargeBy110V()
      {
          Console.WriteLine("美国供电器,正在以110V电压为您充电");
          return 110;
      }
    }
    // 类适配器,通过继承现有接口来完成对现有接口的扩展
    public class Adpater : AmericanCharger ,Target
    {
      public int chargeBy220V()
      {
          int americanCharger = chargeBy110V();
          int charger = americanCharger + 110;
          Console.WriteLine("再加110V,达到220V");
          return charger;
      }
    }
    
    public class Client
    {
      public void main(string[] args)
      {
          Adpater adapter = new Adpater();
          adapter.chargeBy220V();
      }
    
      /*
       * 美国供电器,正在以110V电压为您充电
       * 再加110V,达到220V
       */
    }
  • 对象适配器模式:

    namespace Test.dApadters
    {
      public interface ATarget
      {
          public int chargeBy220V();
      }
      public interface BTarget
      {
          public int chargeBy110V();
      }
    
      public class AmericanCharger : BTarget
      {
          public int chargeBy110V()
          {
              Console.WriteLine("美国充电器, 正在通过110V电压为您充电");
              return 110;
          }
      }
    
      public class ChinaCharger : ATarget
      {
          public int chargeBy220V()
          {
              Console.WriteLine("中国充电器, 正在通过220V电压为您充电");
              return 220;
          }
      }
      public class Adpater : ATarget, BTarget
      {
          private ATarget aTarget;
          private BTarget bTarget;
    
          public Adpater(ATarget charger)
          {
              this.aTarget = charger;
          }
    
          public Adpater(BTarget charger)
          {
              this.bTarget = charger;
          }
    
          public int chargeBy110V()
          {
              int charge = aTarget.chargeBy220V();
              charge -= 110;
            Console.WriteLine("将220V降到110V输出");
              return charge;
          }
    
          public int chargeBy220V()
          {
              int charge = bTarget.chargeBy110V();
              charge += 110;
            Console.WriteLine("将110V升到220V输出");
              return charge;
          }
      }
    
      public class Client
      {
          public void main(String[] args)
          {
              // 110V 转 220V
              BTarget americanCharger = new AmericanCharger();
              Adpater adpater = new Adpater(americanCharger);
              adpater.chargeBy220V();
    
              // 220V 转 110V
              ATarget chinaCharge = new ChinaCharger();
              adpater = new Adpater(chinaCharge);
              adpater.chargeBy110V();
              /*
              *美国充电器, 正在通过110V电压为您充电
              *将110V升到220V输出
              *中国充电器, 正在通过220V电压为您充电
              *将220V降到110V输出
              */
    
          }
      }
    }
    

上述代码用的是双适配器模式,即可以在美国用220V进行充电,也可以在中国用110V进行充电。

1.4 优缺点

  • 优点:
    1. 将目标类和适配者进行解耦:通过引入一个适配器类来重用现有的适配者类,无须修改原有逻辑。
    2. 复用性:它是原本接口不兼容而不能使用的类变的可复用。
    3. 灵活性和扩展性:只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
  • 缺点:
    1. 系统可能变的混乱:过多地使用适配器会让系统变得零乱,不容易整体把握。
    2. 增加系统复杂性:引入适配器模式会增加系统的抽象性,理解和维护相对困难。

1.5 适用场景

  1. 对现有接口进行适配:当一个类需要适用一个已有的接口,但是接口的方法不符合需求时,可以适用适配器模式将该接口适配到需要的形式。
  2. 兼容多个版本或不同类库:如果需要在不同版本或不同类库之间进行交互,适配器模式提供了一个中间层,将不同的接口进行适配。
  3. 封装第三方组件:当需要使用一个第三方组件并希望与该组件解耦时,适配器模式可以第三方组件进行封装,使代码更灵活。

二、桥接模式

2.1 概述

桥接模式是一种一种很实用的结构性设计模式,如果系统中的某个类存在两个独立变化的维度,通过桥接模式可以将这两个维度分离开来,使两者可以独立扩展。桥接模式用抽象关联来取代传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使系统更加灵活,并易于扩展。

定义:将抽象部分与实现部分解耦,使得两者都能够独立变化。

桥接模式最重要的是理解维度,比如蜡笔和毛笔两者都能用作于画画的工作,假如需要大、中、小三种型号的画笔,其中又需要12种颜色的画笔,如果用蜡笔的话则需要36支颜色的蜡笔,而使用毛笔则只需要3支不同型号的毛笔加上12种颜色的调色板,而且如果需要加一种颜色或者加一种型号则需要添加12支蜡笔,而毛笔只需要添加一种型号或者一种颜色即可。毛笔属于将型号和颜色划分为了两个维度,而蜡笔只有一个维度,桥接模式就是将抽象部分和实现部分划为了两个维度

2.2 模式结构

桥接模式一共有4个角色:

  1. Abstraction(抽象类):它是用于定义抽象类的接口,通常是抽象类而不是接口,其中定义了一个Implementor(实现接口)类型的对象并可以维护该对象,他与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
  2. Refine Abstraction(扩充抽象类):它扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,实现了在Abstraction中声明的抽象业务方法,在Refine Abstraction中可以调用Implementor中定义的业务方法。
  3. Implementor(实现类接口):它定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,一般而言,Implementor接口仅仅只提供基本操作,而Abstraction定义的接口可能会做更多复杂的操作,Implementor接口对这些基本操作进行了声明,而具体实现交给其子类,通过关联关系,在Abstraction中不仅拥有自己的方法,而且还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系
  4. Concrete Implementor(具体实现类):实现了Implementor接口,在不同的Concrete Implementor中提供基本操作的不同实现。

image-20240627231148675

2.3 实现与应用实际

设计一个系统,要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件,并且能够在Windows、Linux、UNIX等多个操作系统上运行,系统需要将各种格式的文件解析成像素矩阵,然后将像素矩阵显示再屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。

2.3.1 Abstarction抽象类

public abstract class Image
{
    protected ImageImp imp;
    public void SetImageImp(ImageImp imp)
    {
        this.imp = imp;
    }

    public abstract void parseFile(string filename);
}

2.3.2 Refine Abstraction扩充抽象类

public class JPGImage : Image
{
    public override void parseFile(string filename)
    {
        // 模拟解析一个JPG文件并获得一个像素矩阵对象m
        Matrix matrix = new Matrix();
        imp.DoPaint(matrix);
        Console.WriteLine(filename + ", 格式为JPG");
    }
}

public class PNGImage : Image
{
    public override void parseFile(string filename)
    {
        // 模拟解析一个PNG文件并获得一个像素矩阵对象m
        Matrix matrix = new Matrix();
        imp.DoPaint(matrix);
        Console.WriteLine(filename + ", 格式为PNG");
    }
}

public class GIFImage : Image
{
    public override void parseFile(string filename)
    {
        // 模拟解析一个GIF文件并获得一个像素矩阵对象m
        Matrix matrix = new Matrix();
        imp.DoPaint(matrix);
        Console.WriteLine(filename + ", 格式为GIF");
    }
}

2.3.3 Implementor实现接口

    // 像素矩阵类,辅助类
    public class Matrix
    {

    }

    public interface ImageImp
    {
        public void DoPaint(Matrix matrix); // 显示像素矩阵
    }

2.3.4 Concrete Implementor

public class WindowsImp : ImageImp
{
    public void DoPaint(Matrix matrix)
    {
        // 调用Windows系统的绘制函数绘制像素矩阵
        Console.WriteLine("在Windows操作系统中显示图像");
    }
}

public class LinuxImp : ImageImp
{
    public void DoPaint(Matrix matrix)
    {
        // 调用Linux系统的绘制函数绘制像素矩阵
        Console.WriteLine("在Linux操作系统中显示图像");
    }
}

public class UnixImp : ImageImp
{
    public void DoPaint(Matrix matrix)
    {
        // 调用Unix系统的绘制函数绘制像素矩阵
        Console.WriteLine("在Unix操作系统中显示图像");
    }
}

public abstract class Image
{
    protected ImageImp imp;
    public void SetImageImp(ImageImp imp)
    {
        this.imp = imp;
    }

    public abstract void parseFile(string filename);
}

2.4 优缺点

  • 优点:
    1. 分离抽象与实现:将抽象部分与具体实现部分分离,使它们可以独立变化,允许我们在不影响彼此的情况下对它进行扩展
    2. 提高扩展性:通过桥接模式,我们可以在两个变化维度中的任意一个进行扩展,而无须修改原有系统。
  • 缺点:
    1. 增加系统的复杂性:桥接模式会增加系统的理解和设计难度,由于聚合关联关系简历抽象层,需要对抽象层进行设计和编程。
    2. 理解难度增加:正确识别系统中的两个独立维度需要一定经验。

2.5 适用场景

  1. 存在独立变化维度:一个类存在两个独立变化维度,且这两个维度都需要扩展。
  2. 抽象实现灵活:当抽象部分和具体实现部分之间有更多的灵活性,可适用桥接模式,可以避免两个层次之前使用静态的继承关系,而是通过组合建立关联关系。