适配器模式

简介

适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式通常用于解决两个不兼容接口之间的兼容性问题,使得原本由于接口不匹配而无法一起工作的类能够协同工作。

适配器模式主要由三个角色组成:

  1. 目标接口(Target Interface):客户端所期望使用的接口。适配器模式的目标是使得原本不兼容的接口能够通过目标接口进行访问。

  2. 源接口(Adaptee):需要被适配的接口。这是原始的接口,其功能可能与目标接口不匹配。

  3. 适配器(Adapter):这是一个具体的类,它实现了目标接口,并且包装了一个或多个源接口的实例。适配器接收来自客户端的请求,并将这些请求转发给源接口的实例,以便与源接口协同工作。

适配器模式的实现可以通过对象适配器或类适配器来完成:

  • 对象适配器:适配器类将源接口的实例作为一个成员变量,然后实现目标接口。通过这种方式,适配器可以调用源接口的方法来实现目标接口的方法。

  • 类适配器:适配器类同时继承了目标接口和源接口。通过继承源接口,适配器可以直接调用源接口的方法,并通过实现目标接口来向客户端暴露适配后的接口。

案例

对象适配器

对象适配器模式在现实生活中的一个例子是使用不同种类的插头适配器来连接不同国家的电器。

想象一下你旅行到了一个国家,但是你带的电器插头与当地的插座不兼容。这时你需要一个插头适配器来解决这个问题。这个适配器就是对象适配器的一个例子。

具体来说,让我们以欧洲和美国的电器插头为例:

  • 在欧洲,常见的电器插头是双圆形的欧洲标准插头。
  • 在美国,常见的电器插头是两个扁平的插脚的美国标准插头。

如果你带了一个欧洲的电器到美国,你会发现插头无法直接插入美国的插座。这时,你可以使用一个插头适配器。这个适配器有一个欧洲标准插座的接口,以及一个美国标准插座的接口。当你插入这个适配器时,它会将欧洲标准插头的电器接口转换为美国标准插座的接口,这样你的电器就可以在美国正常使用了。

在这个例子中:

  • 目标接口是美国标准插座。
  • 源接口是欧洲标准插座。
  • 适配器是插头适配器,它包装了一个欧洲标准插座的实例,并实现了美国标准插座的接口。

通过这个适配器,你的电器就能够在美国的插座上正常运行,实现了不同标准之间的兼容性。

using System;

// 欧洲标准插座接口
interface IEuropeanSocket
{
    void ProvideElectricity();
}

// 欧洲标准插座实现
class EuropeanSocket : IEuropeanSocket
{
    public void ProvideElectricity()
    {
        Console.WriteLine("欧洲标准插座供电,使用双圆形插头。");
    }
}

// 美国标准插座接口
interface IUSASocket
{
    void SupplyElectricity();
}

// 美国标准插座实现
class USASocket : IUSASocket
{
    public void SupplyElectricity()
    {
        Console.WriteLine("美国标准插座供电,使用两个扁平插脚。");
    }
}

// 适配器类
class SocketAdapter : IUSASocket
{
    private IEuropeanSocket _europeanSocket;

    public SocketAdapter(IEuropeanSocket europeanSocket)
    {
        _europeanSocket = europeanSocket;
    }

    public void SupplyElectricity()
    {
        // 插头转换过程
        ConvertPlug();

        // 提供电源
        Console.WriteLine("2. 提供电源。");

        // 通知开始供电
        Console.WriteLine("3. 开始供电。");

        // 调用欧洲标准插座的方法
        _europeanSocket.ProvideElectricity();
    }

    private void ConvertPlug()
    {
        Console.WriteLine("1. 插头转换:将双圆形插头转换为两个扁平插脚。");
    }
}

// 客户端代码
class Program
{
    static void Main(string[] args)
    {
        // 创建一个欧洲标准插座
        IEuropeanSocket europeanSocket = new EuropeanSocket();

        // 创建一个适配器,将欧洲标准插座适配成美国标准插座
        SocketAdapter adapter = new SocketAdapter(europeanSocket);

        // 在美国使用适配后的插座
        Console.WriteLine("在美国使用适配后的插座:");
        adapter.SupplyElectricity();

        Console.ReadLine();
    }
}

类图

@startuml

interface IEuropeanSocket {
    {abstract} +ProvideElectricity()
}

class EuropeanSocket {
    +ProvideElectricity()
}

IEuropeanSocket <|.. EuropeanSocket

interface IUSASocket {
    {abstract} +SupplyElectricity()
}

class USASocket {
    +SupplyElectricity()
}

IUSASocket <|.. USASocket

class SocketAdapter {
    -_europeanSocket: IEuropeanSocket
    +SocketAdapter(europeanSocket: IEuropeanSocket)
    +SupplyElectricity()
    -ConvertPlug()
}

IUSASocket <|.. SocketAdapter
SocketAdapter --> IEuropeanSocket : Uses
@enduml
代码类图

 

类适配器

类适配器是通过多重继承实现的,这在许多编程语言中是不被支持的。在C#中,类适配器通常不太容易实现,因为C#不支持多重继承。不过,我们可以通过接口实现类似的功能。

以下是一个类似于类适配器的生活场景的例子:

假设你有一个游戏控制器类 GameController,它有一个方法 Move() 用于移动角色。现在你想要为这个控制器类添加一个新的方法 Jump(),但是由于某些原因,你无法直接修改 GameController 类。你可以使用适配器模式来解决这个问题。

首先,我们有 GameController 类和 Jump() 方法:

// 游戏控制器类
class GameController
{
    public void Move()
    {
        Console.WriteLine("移动角色");
    }
}

然后,我们创建一个接口 IJumpable,其中包含了 Jump() 方法:

// 跳跃接口
interface IJumpable
{
    void Jump();
}

接下来,我们创建一个适配器类 JumpAdapter,它继承了 GameController 类并实现了 IJumpable 接口,以添加跳跃功能:

// 适配器类(类适配器)
class JumpAdapter : GameController, IJumpable
{
    public void Jump()
    {
        Console.WriteLine("角色跳跃");
    }
}

现在,我们可以在客户端代码中使用适配器来移动和跳跃:

class Program
{
    static void Main(string[] args)
    {
        // 使用适配器实现移动和跳跃
        Jumpable jumpController = new JumpAdapter();
        jumpController.Move();
        jumpController.Jump();

        Console.ReadLine();
    }
}

完整代码

using System;

// 游戏控制器类
class GameController
{
    public void Move()
    {
        Console.WriteLine("移动角色");
    }
}

// 跳跃接口
interface IJumpable
{
    void Jump();
}

// 适配器类(类适配器)
class JumpAdapter : GameController, IJumpable
{
    public void Jump()
    {
        Console.WriteLine("角色跳跃");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 使用适配器实现移动和跳跃
        IJumpable jumpController = new JumpAdapter();
        jumpController.Move();
        jumpController.Jump();

        Console.ReadLine();
    }
}

项目实际案例

在 GUI 开发中,开始时使用 WinForms 来开发用户界面,并且使用了 FlowLayoutPanel 来管理布局。但是后来决定将用户界面迁移到 WPF,并使用 StackPanel 来管理布局。

由于项目时间关系,考虑要保持原有的WinForms 布局不变,为了实现平滑的迁移,可以使用适配器模式。

以下是一个简化的示例:

首先是 WinForms 中使用的 FlowLayoutPanel:

using System.Windows.Forms;

public class WinFormsFlowLayout
{
    private readonly FlowLayoutPanel _flowLayoutPanel;

    public WinFormsFlowLayout()
    {
        _flowLayoutPanel = new FlowLayoutPanel();
    }

    public void AddControl(Control control)
    {
        _flowLayoutPanel.Controls.Add(control);
    }

    // 其他方法...
}

现在我们决定迁移到 WPF 并使用 StackPanel,我们可以创建一个适配器来适配 StackPanel:

using System.Windows.Controls;

public class WpfStackPanelAdapter : WinFormsFlowLayout
{
    private readonly StackPanel _stackPanel;

    public WpfStackPanelAdapter()
    {
        _stackPanel = new StackPanel();
    }

    public override void AddControl(Control control)
    {
        _stackPanel.Children.Add(new WindowsFormsHost { Child = control });
    }

    // 其他方法...
}

在这个例子中,WpfStackPanelAdapter 类继承自 WinFormsFlowLayout 类,但是重写了 AddControl() 方法。在重写的方法中,我们使用了 WindowsFormsHost 来将 WinForms 控件嵌入到 WPF 中的 StackPanel 中,以实现对 StackPanel 的适配。

通过这种方式,我们可以实现平滑的从 WinForms 到 WPF 的迁移,并且保留了原有的布局管理器接口,从而减少了对现有代码的修改。

优点:

  1. 增强灵活性:适配器模式允许客户端使用不兼容的接口,从而增强了系统的灵活性。它可以将现有的类与其他类协作,而无需修改其源代码。

  2. 重用现有功能:适配器模式可以重用现有类的功能,而无需修改其现有代码。这使得系统更容易维护和扩展。

  3. 解耦合:适配器模式将客户端与具体类解耦,客户端只需要与适配器交互,而不需要了解被适配的类的细节。

  4. 统一接口:适配器模式可以将多个类的接口统一成一个接口,使得客户端可以统一调用不同类的方法。

缺点:

  1. 增加复杂性:适配器模式引入了一个额外的适配器类,可能会增加系统的复杂性。

  2. 潜在性能损耗:适配器模式可能会引入一些性能损耗,因为适配器需要转换接口并委托给被适配的对象。

  3. 过多使用可能会导致设计问题:如果过度使用适配器模式,可能会导致系统中出现大量的适配器类,使得系统变得复杂和难以理解。因此,适配器模式应谨慎使用,只在需要时才使用。

 适用场景:

适配器模式通常在以下情况下使用:

  1. 使用现有类:当系统需要使用已经存在的类,但是其接口与系统要求的接口不匹配时,可以使用适配器模式。这样可以避免修改现有类的代码,同时也能够满足系统的需求。

  2. 集成第三方库:当需要集成第三方库或外部系统,并且它们的接口与系统接口不兼容时,可以使用适配器模式来进行集成,使得系统能够与第三方库无缝交互。

  3. 统一接口:当系统中存在多个类具有类似但不同的接口时,可以使用适配器模式将它们统一成一个接口,使得客户端可以统一调用不同类的方法。

  4. 复用已有功能:当需要复用现有类的功能,但是现有类的接口与系统要求的接口不匹配时,可以使用适配器模式来封装现有类,并将其接口适配成系统所需的接口。

  5. 透明地封装类:当需要将某个类的实现细节对客户端透明地隐藏时,可以使用适配器模式来封装该类,从而达到解耦合的目的。

总之,适配器模式适用于需要解决接口不兼容问题、统一接口、复用现有功能、透明封装类等场景。

posted @ 2024-02-28 10:26  咸鱼翻身?  阅读(17)  评论(0编辑  收藏  举报