65.9K
CodeProject 正在变化。 阅读更多。
Home

接口隔离原则(C# 中 SOLID 的 ISP)- 历史回顾

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (21投票s)

2014年4月29日

CPOL

3分钟阅读

viewsIcon

69719

接口隔离原则 - 历史回顾

引言

本技巧解释了什么是接口隔离原则以及它的用途。它面向初学者和中级开发人员。

接口隔离原则

该原则指出,不应该强迫客户端依赖于它不使用的方法(维基百科)。

换句话说,“为一个没有马的人卖马鞍有什么意义?”

免责声明:以下讨论的灵感来自维基百科。您会发现这些例子很相似,但为了便于理解而进行了详细说明。

历史

让我们从一个小故事开始。

别担心 :) 这会很有趣。

假设一家公司,施乐,创造了一个新的多功能打印机系统。该打印机系统可以执行各种各样的工作,如打印、扫描、装订、传真等。

施乐聘请开发人员为该打印机开发软件。很快,软件开发完成,打印机工作良好。

但随着时间的推移,维护软件变得困难,进一步开发简直是一场噩梦。为什么?好吧,我们将在短时间内看到。 J

施乐做了什么?

施乐聘请了一位顾问来查看问题所在。我们的英雄马丁来了。我们将看到问题是什么,马丁如何提出解决方案以及 ISP 的诞生。

有问题的代码片段如下

// The code that violates ISP
interface IMachine
{
    public bool print(List<Item> item);
    public bool staple(List<Item> item);
    public bool fax(List<Item> item);
    public bool scan(List<Item> item);
    public bool photoCopy(List<Item> item);
}  
// Code implementing the IMachine interface. 

class Machine : IMachine
{
   public Machine()
   {
   }

   public bool print(List<Item> item)
   { 
      // Print the items.
      Console.WriteLine("All Items printed" + item.Count());
   }

   public bool staple(List<Item> item) 
   {
      // Staple the items.
      Console.WriteLine("Items stapled" + item.Count());
   }
 
   public bool fax(List<Item> item) 
   {
      // Fax the items.
      Console.WriteLine("All Items Faxed" + item.Count());
   }

   public bool scan(List<Item> item) 
   {
      // Scan the items.
      Console.WriteLine("All Items Scanned" + item.Count());
   }

   public bool photoCopy(List<Item> item) 
   {
      // Xerox the items.
      Console.WriteLine("All Items Photo copied" + item.Count()); 
   }
}  

嗯,这是一个简单的代码。但是,等等!

在向下滚动之前,花一分钟时间列出上述代码的潜在问题。

问题和陷阱

好的,我们开始。 J

问题

1. 即使必须进行微小的更改,也要重新编译整个代码 L

即使您进行小的修改,也需要重新编译整个代码,从而浪费宝贵的开发时间。

2. 当您将此提供给仅对打印工作感兴趣的客户时会发生什么?

您正在传递一个 DLL 或一个包含一堆不必要函数的库,这些函数并非您的客户都需要。他或她可能会对这些功能感到困惑。将打印机功能传递给打印机客户端是有意义的。但是,传递一堆不相关的功能有什么好处?

3. “胖”接口的问题

即使打印功能是打印机客户端唯一需要的功能,他/她最终可能会实现一些不需要的功能。即使您的打印机客户端不想要扫描功能,您为什么要强迫他们实现扫描功能?

那么马丁做了什么?

马丁提出了一个解决方案,催生了接口隔离原则。

这就是

  1. 将胖接口分解为更小且更有意义的角色接口。
  2. 在上面的示例中,让 IMachine 接口包含所有分解后的接口 :)。
  3. 将较小接口的实现注入到 Machine 类中(依赖注入)。
// (The following interfaces are called as “Role Interfaces” as they serve their roles :))

interface IPrinter
{
    public bool Print(List<Item> Items);
}

class Printer : IPrinter
{
    public bool Print(List<Item> Items)
    {
       foreach(var item in Item)
          print(item);
    }

    //Other definitions...
}

interface IStaple
{
    public bool Staple(List<Item> Items);
}

class Staple : IStaple
{
    public bool Staple(List<Item> Items)
    {
       foreach(var item in Items) 
          Staple(item);
    }

    //Other definitions...
}

interface IFax
{    
    public bool Fax(List<Item> Items);
}

class Fax : IFax
{
    public bool Fax(List<Item> Items)
    {
        foreach(var item in Items) 
           Fax(item);
    }

    //Other definitions...
}

interface IScan
{
    public bool Scan(List<Item> Items);
}

class Scan : IScan
{
    public bool Scan(List<Item> Items)
    {
        foreach(var item in Items)
           Scan(item);
    }

    //Other definitions...
}

interface IPhotoCopy
{
    public bool PhotoCopy(List<Item> Items);
}

class PhotoCopy : IPhotoCopy
{
    public bool PhotoCopy(List<Item> Items)
    {
        foreach(var item in Items)
           PhotoCopy(item);
    }

    //Other definitions...
}

interface IMachine : IPrinter, IFax, IScan, IPhotoCopy,IStaple
{
    public bool print(List<Item> item);
    public bool staple(List<Item> item);
    public bool fax(List<Item> item);
    public bool scan(List<Item> item);
    public bool photoCopy(List<Item> item);
}

class Machine : IMachine
{
      private IPrinter printer {get;set;}
      private IFax fax {get;set;}
      private IScan scan  {get;set;}
      private IPhotoCopy photocopy {get;set;}
      private IStaple staple {get;set;}
  
      // Notice how the dependencies are injected through constructor (constructor dependency injection)
      public  Machine(IPrinter printer, IFax fax, IScan scan, IPhotoCopy photoCopy, IStaple staple)
      {
         this.Printer = printer;
        this.fax = fax;    
        this.scan = scan;
        this.photoCopy = photocopy;
        this.staple = staple;
          }

      public bool print(List<Item> item)
         { 
          // Print the items.
          Console.WriteLine("All Items printed" + item.Count());
      }

      public bool staple(List<Item> item) 
      {
          // Staple the items.
          Console.WriteLine("Items stapled" + item.Count());
      }
 
      public bool fax(List<Item> item) 
      {
          // Fax the items.
          Console.WriteLine("All Items Faxed" + item.Count());
      }

      public bool scan(List<Item> item) 
      {
          // Scan the items.
          Console.WriteLine("All Items Scanned" + item.Count());
      }

      public bool photoCopy(List<Item> item) 
      {
          // Xerox the items.
          Console.WriteLine("All Items Photo copied" + item.Count()); 
      }
}

之后会发生什么?

1. 只有打印机客户端来了。该怎么办?

这很简单。创建一个 Printer 类的实例并给他/她。

    IPrinter printer = new Printer();
    
    printer.print( //IDE shows that only the Print function is accessible. 
    // Clean, neat and separate.   

请注意,这里我们没有强迫他使用具有不需要的功能(如扫描、传真等)的库。我们的客户只想要打印机功能,我们使用我们的 "IPrinter" 角色接口为他提供了他所要求的。我们的客户现在很高兴,因为他得到了他想要的。 :)

2. 有一个想要所有功能的客户来了。该怎么办?

很简单。

    IPrinter printer = new Printer();     //Printer implements IPrinter
    IScan scanner = new Scanner();
    IFax fax = new Fax();
    IPhotoCopy photocopy = new PhotoCopy();
    IStaple staple = new Staple();

    var allOneClient  = new Machine(printer, fax, scan, photoCopy, staple);  

感觉不错。嗯?

就这样,我结束了马丁如何制定 ISP。

结束语

维基百科说

“接口隔离原则 (ISP) 指出,不应该强迫客户端依赖于它不使用的方法。”

  1. ISP 将非常大的接口拆分为更小更具体的接口,以便客户端只需要了解他们感兴趣的方法。
  2. 这种缩小的接口也称为角色接口
  3. ISP 旨在保持系统解耦,从而更容易重构、更改和重新部署。

我希望上面的例子涵盖了每一个要点。快乐的 OODing :)

© . All rights reserved.