使用接口重构

已完成

在上一课中,你了解了紧密耦合代码的挑战,以及它如何违反 开放/封闭原则。 现在,让我们重构 Library 和 BorrowableBook 示例,使系统更加灵活且更易于维护。

下面是演示问题的原始代码:

public class BorrowableBook
{
    public string Title { get; set; }
    public bool IsAvailable { get; private set; } = true;

    public BorrowableBook(string title)
    {
        Title = title;
    }

    public void Borrow()
    {
        if (IsAvailable)
        {
            IsAvailable = false;
            Console.WriteLine($"You have borrowed \"{Title}\".");
        }
        else
        {
            Console.WriteLine($"\"{Title}\" is already borrowed.");
        }
    }
}

public class Library
{
    private BorrowableBook _book;

    public Library(BorrowableBook book)
    {
        _book = book;
    }

    public void BorrowBook()
    {
        if (_book.IsAvailable)
        {
            _book.Borrow();
        }
        else
        {
            Console.WriteLine("The book is not available.");
        }
    }
}

这种设计将Library类与BorrowableBook类紧密耦合,导致在不修改Library类的情况下很难添加新的可借用物品类型(例如 DVD)。 为了修复紧密耦合,我们引入了一个接口来分离借用行为。

接口简介

接口设置行为协定,而无需详细说明其实现。 引入接口会抽象化借用功能,并提高系统的灵活性。

下面是 IBorrowable 接口:

public interface IBorrowable
{
    bool IsAvailable { get; }
    void Borrow();
}

此接口引入了两个成员:

  • IsAvailable:一个属性,指示该项是否可用于借款。
  • Borrow:借用项的方法。

通过定义此接口,我们将借用行为与任何特定实现分离。

重构 BorrowableBook 类

接下来,我们将更新 BorrowableBook 类以实现 IBorrowable 接口:

public class BorrowableBook : IBorrowable
{
    public string Title { get; set; }
    public bool IsAvailable { get; private set; } = true;

    public BorrowableBook(string title)
    {
        Title = title;
    }

    public void Borrow()
    {
        if (IsAvailable)
        {
            IsAvailable = false;
            Console.WriteLine($"You have borrowed \"{Title}\".");
        }
        else
        {
            Console.WriteLine($"\"{Title}\" is already borrowed.");
        }
    }
}

现在,该 BorrowableBook 类遵循 IBorrowable 接口,使其与其他实现同一接口的类互换。

重构 Library 类

我们还将 Library 类更新为依赖于 IBorrowable 接口而不是具体 BorrowableBook 类:

public class Library
{
    private IBorrowable _item;

    public Library(IBorrowable item)
    {
        _item = item;
    }

    public void BorrowItem()
    {
        if (_item.IsAvailable)
        {
            _item.Borrow();
        }
        else
        {
            Console.WriteLine("The item is not available.");
        }
    }
}

现在,该 Library 类可以使用任何实现 IBorrowable 接口的对象,使其更加灵活且更易于扩展。

使用依赖项注入

假设你正在设置家庭娱乐系统。 与其将特定品牌的扬声器永久连接到立体声设备,不如使用可以接受多种兼容扬声器插头的扬声器“插孔”。 此设计使你可以轻松地更换或升级扬声器,而无需更改整个立体声系统。

在软件中, 依赖关系注入 的工作方式类似。 它允许类依赖于抽象接口,而不是特定实现。 依赖项注入使系统更加灵活且更易于维护,因为无需修改类本身即可“插入”不同的实现。

编程中的构造函数就像技术人员在设置立体声系统时连接扬声器插孔一样。 对于Library类,构造函数public Library(IBorrowable item)是提供依赖项的地方。 构造函数允许 Library 类处理任何兼容的实现,例如 BorrowableBook 或 BorrowableDVD,不需要更改其内部结构。 正如扬声器插孔能够灵活地选择不同的扬声器一样,构造函数可以灵活地使用各种可借用项目。

下面是它在代码中的工作方式:

public class Library
{
    private IBorrowable _item;

    public Library(IBorrowable item) // Dependency is injected here
    {
        _item = item;
    }

    public void BorrowItem()
    {
        if (_item.IsAvailable)
        {
            _item.Borrow();
        }
        else
        {
            Console.WriteLine("The item is not available.");
        }
    }
}

在本示例中:

  • 构造函数是生成“连接”( 依赖项)的位置,类似于扬声器插件连接到“插孔”的方式。IBorrowable
  • 该 IBorrowable 接口的作用类似于“立体声插孔”,用于定义标准连接点。
  • BorrowableBook和BorrowableDVD就像不同类型的立体声扬声器,每种都有其独特的连接器,可以连接到“插孔”。

通过使用依赖关系注入,该 Library 类可以处理任何实现 IBorrowable。 此方法提供:

  • 灵活性:无需修改 Library 类即可轻松切换或添加新实现。
  • 可测试性:可以“插入”模拟实现进行测试。
  • 可维护性:类 Library 不需要知道特定实现的详细信息,因此更易于扩展和维护。

此设计可确保 Library 类不再紧密耦合到特定的实现,使系统更具模块化和适应性。

添加新的可借项

有了接口,我们可以轻松地添加新类型的可借项,而无需修改 Library 类。 例如,下面是一个 BorrowableDVD 类:

public class BorrowableDVD : IBorrowable
{
    public string Title { get; set; }
    public bool IsAvailable { get; private set; } = true;

    public BorrowableDVD(string title)
    {
        Title = title;
    }

    public void Borrow()
    {
        if (IsAvailable)
        {
            IsAvailable = false;
            Console.WriteLine($"You have borrowed the DVD \"{Title}\".");
        }
        else
        {
            Console.WriteLine($"The DVD \"{Title}\" is already borrowed.");
        }
    }
}

该 BorrowableDVD 类实现相同的 IBorrowable 接口,因此可以与 Library 类无缝使用。

测试系统

下面是一个演示重构系统的程序:

using System;

class Program
{
    static void Main()
    {
        // Create borrowable items
        IBorrowable book = new BorrowableBook("Adventure Works Cycles");
        IBorrowable dvd = new BorrowableDVD("Graphic Design Institute");

        // Create libraries
        Library bookLibrary = new Library(book);
        Library dvdLibrary = new Library(dvd);

        // Borrow items
        bookLibrary.BorrowItem();
        bookLibrary.BorrowItem(); // Try borrowing again

        Console.WriteLine();

        dvdLibrary.BorrowItem();
        dvdLibrary.BorrowItem(); // Try borrowing again
    }
}

输出演示重构系统的灵活性:

You have borrowed "Adventure Works Cycles".
"Adventure Works Cycles" is already borrowed.

You have borrowed the DVD "Graphic Design Institute".
The DVD "Graphic Design Institute" is already borrowed.

此重构的示例演示了接口如何减少依赖项并提高模块化性:

  • 关注点分离:接口 IBorrowable 隔离借用行为,确保 Library 类不依赖于特定实现。
  • 改进了灵活性:无需修改 Library 类即可添加新类型的可借款项(例如 DVD)。
  • 简化的维护:系统更易于理解、测试和扩展,因为职责明显存在分歧。