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






4.53/5 (21投票s)
接口隔离原则 - 历史回顾
引言
本技巧解释了什么是接口隔离原则以及它的用途。它面向初学者和中级开发人员。
接口隔离原则
该原则指出,不应该强迫客户端依赖于它不使用的方法(维基百科)。
换句话说,“为一个没有马的人卖马鞍有什么意义?”
免责声明:以下讨论的灵感来自维基百科。您会发现这些例子很相似,但为了便于理解而进行了详细说明。
历史
让我们从一个小故事开始。
别担心 :) 这会很有趣。
假设一家公司,施乐,创造了一个新的多功能打印机系统。该打印机系统可以执行各种各样的工作,如打印、扫描、装订、传真等。
施乐聘请开发人员为该打印机开发软件。很快,软件开发完成,打印机工作良好。
但随着时间的推移,维护软件变得困难,进一步开发简直是一场噩梦。为什么?好吧,我们将在短时间内看到。 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. “胖”接口的问题
即使打印功能是打印机客户端唯一需要的功能,他/她最终可能会实现一些不需要的功能。即使您的打印机客户端不想要扫描功能,您为什么要强迫他们实现扫描功能?
那么马丁做了什么?
马丁提出了一个解决方案,催生了接口隔离原则。
这就是
- 将胖接口分解为更小且更有意义的角色接口。
- 在上面的示例中,让
IMachine
接口包含所有分解后的接口 :)。 - 将较小接口的实现注入到 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) 指出,不应该强迫客户端依赖于它不使用的方法。”
- ISP 将非常大的接口拆分为更小更具体的接口,以便客户端只需要了解他们感兴趣的方法。
- 这种缩小的接口也称为角色接口。
- ISP 旨在保持系统解耦,从而更容易重构、更改和重新部署。
我希望上面的例子涵盖了每一个要点。快乐的 OODing :)