我的设计模式之旅、13 适配器模式

编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。

思考总结

思考问题

程序调用第三方库经常会遇到的问题?

image-20220919124648597

你可能根本没有程序库的源代码,从而无法对其进行修改。

什么是适配器模式

适配器是一种结构型设计模式, 它能使接口不兼容的对象能相互合作。

适配器模式:将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能和一起工作的那些类可以一起工作。

image-20220919124816442

含义:

  • 适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。
  • 适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。

工作方式:

  • 适配器实现与其中一个现有对象兼容的接口。
  • 现有对象可以使用该接口安全地调用适配器方法。
  • 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。有时你甚至可以创建一个双向适配器来实现双向转换调用。

何时使用:

  • 当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。
  • 适配器模式允许你创建一个中间层类,其可作为代码与遗留类、第三方类或提供怪异接口的类之间的转换器。
  • 如果您需要复用这样一些类,他们处于同一个继承体系,并且他们又有了额外的一些共同的方法,但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。这种方式同装饰模式非常相似。
    • 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

实现方式:

  • 确保至少有两个类的接口不兼容。
    • 一个无法修改(通常是第三方、遗留系统或者存在众多已有依赖的类)的功能性服务类。
    • 一个或多个将受益于使用服务类的客户端类。
  • 声明客户端接口,描述客户端如何与服务交互。
  • 创建遵循客户端接口的适配器类。所有方法暂时都为空。
  • 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。
  • 依次实现适配器类客户端接口的所有方法。适配器会将实际工作委派给服务对象,自身只负责接口或数据格式的转换。
  • 客户端必须通过客户端接口使用适配器。这样一来,你就可以在不影响客户端代码的情况下修改或扩展适配器。

应用实例:

  • 美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。
  • 在 LINUX 上运行 WINDOWS 程序。

优点:

  • 可以让任何两个没有关联的类一起运行。
  • 提高了类的复用。 灵活性好。
  • 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点:

  • 代码整体复杂度增加,因为你需要新增一系列接口和类。过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

注意事项:

  • 适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

与其他模式的关系:

  • 桥接通常会于开发前期进行设计,使你能够将程序的各个部分独立开来以便开发。另一方面,适配器通常在已有程序中使用,让相互不兼容的类能很好地合作。
  • 适配器可以对已有对象的接口进行修改,装饰能在不改变对象接口的前提下强化对象功能。此外,装饰还支持递归组合,适配器则无法实现。
  • 适配器能为被封装对象提供不同的接口,代理能为对象提供相同的接口,装饰则能为对象提供加强的接口。
  • 外观为现有对象定义了一个新接口,适配器则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
  • 桥接、状态和策略(在某种程度上包括适配器)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,各自解决了不同的问题。

C# 解决方钉与圆孔问题

Adapter.cs 适配器模式

namespace 适配器模式;

public class RoundHole
{
    public double Radius { private set; get; }

    public RoundHole(double radius)
    {
        Radius = radius;
    }

    public bool fit(Round r)
    {
        return r.getRadius() <= Radius;
    } 
}

public class Round
{
    private double radius;

    protected Round()
    {
        
    }
    
    public Round(double radius)
    {
        this.radius = radius;
    }

    public virtual double getRadius()
    {
        return radius;
    }
}

public class Square
{
    public double Side { private set; get; }
    public Square(double side)
    {
        Side = side;
    }

    public double 对角线一半长()
    {
        return Side / 2 * Math.Sqrt(2);
    }

}

public class RoundAdapter : Round
{
    private Square s; // 也可以是接口
    public RoundAdapter(Square s)
    {
        this.s = s;
    }

    public void setSquare(Square s)
    {
        this.s = s;
    }

    public override double getRadius()
    {
        return s.对角线一半长();
    }
    // ... 数据转换,重写接口方法等
}

Program.cs

using 适配器模式;

RoundHole hole = new(5);
Round r1 = new(1);
Round r2 = new(5);
Round r3 = new(10);
Console.WriteLine(hole.fit(r1));
Console.WriteLine(hole.fit(r2));
Console.WriteLine(hole.fit(r3));

Square s1 = new(5);
RoundAdapter adapter = new(s1);
Console.WriteLine(hole.fit(adapter));
adapter.setSquare(new Square(20));
Console.WriteLine(hole.fit(adapter));

Output

True
True
False
True
False

参考资料

  • 《Go语言核心编程》李文塔
  • 《Go语言高级编程》柴树彬、曹春辉
  • 《大话设计模式》程杰
  • 《深入设计模式》亚历山大·什韦茨
  • 菜鸟教程
posted @ 2022-09-19 19:32  小能日记  阅读(9)  评论(0编辑  收藏  举报