接口隔离原则|SOLID如石

Interface Segregation Principle | SOLID as a Rock


I nterface S egregation P 原则在C++中是第四个&到目前为止是一系列SOLID如磐石设计原则中最简单的设计原则。SOLID设计原则着重于开发易于维护、可重用&可扩展的软件。在这篇文章中,我们将看到一个违反ISP的代码,一个解决相同代码的方法,准则& ISP的好处。

顺便说一下,如果你还没有看过我以前关于设计原则的文章,那么下面是快速链接。

  1.   S RP -- 单一责任原则
  2.   O CP -- 开放/封闭原则
  3.   L SP -- 利斯科夫替代原则
  4.   I SP -- 接口隔离原则
  5.   D IP -- 依赖反转原则

你在这一系列文章中看到的代码片段是简化的而不是复杂的。所以你经常看到我没有使用像 overridefinalpublic 这样的关键词(虽然是继承),只是为了使代码紧凑和amp;在单一的标准屏幕大小中可以消费(大部分时间)。我也喜欢用 struct 而不是 class ,只是为了节省行数,有时不写" public: ",也故意错过 虚拟析构器 、构造器、 复制构造器 、前缀 std:: 、删除动态内存。我也认为自己是一个务实的人,希望用最简单的方式来表达一个想法,而不是用标准的方式或使用Jargons。

注意:

  • 如果你直接在这里跌倒,那么我建议你先看一下 什么是设计模式? ,即使它是微不足道的。我相信这将鼓励你在这个话题上进行更多的探索。
  • 你在这一系列文章中遇到的所有这些代码都是使用C++20编译的(尽管我在大多数情况下使用了 现代C++ 的功能,直到C++17)。因此,如果你没有机会使用最新的编译器,你可以使用 https://wandbox.org/ ,它也有预装的boost库。

意图

不应强迫客户依赖他们不使用的接口。

  • 接口隔离原则与单一责任原则有很大关系。它的真正含义是,你应该始终以这样的方式设计你的抽象,即使用暴露的方法的客户不一定要得到整个馅饼。这让客户承担了实现他们实际上不需要的方法的负担。

违反接口隔离原则

   struct Document;

struct IMachine {
    virtual void print(Document &doc) = 0;
    virtual void fax(Document &doc) = 0;
    virtual void scan(Document &doc) = 0;
};

struct MultiFunctionPrinter : IMachine {      // OK
    void print(Document &doc) override { }
    void fax(Document &doc) override { }
    void scan(Document &doc) override { }
};

struct Scanner : IMachine {                   // Not OK
    void print(Document &doc) override { /* Blank */ }
    void fax(Document &doc) override { /* Blank */ }
    void scan(Document &doc) override {  
        // Do scanning ...
    }
};    
  • 正如你所看到的,就 MultiFunctionPrinter 而言,实现 print() , fax() & scan() 方法由 IMachine 接口执行是可以的。
  • 但是如果你只需要一个 ScannerPrinter , 一些设计仍然继承 IMachine & 留下不必要的方法或者抛出 NotImplemented 异常,无论如何,你都是做错了。

接口隔离原则 例子

   /* -------------------------------- Interfaces ----------------------------- */
struct IPrinter {
    virtual void print(Document &doc) = 0;
};

struct IScanner {
    virtual void scan(Document &doc) = 0;
};
/* ------------------------------------------------------------------------ */

struct Printer : IPrinter {
    void print(Document &doc) override;
};

struct Scanner : IScanner {
    void scan(Document &doc) override;
};

struct IMachine : IPrinter, IScanner { };

struct Machine : IMachine {
    IPrinter&   m_printer;
    IScanner&   m_scanner;

    Machine(IPrinter &p, IScanner &s) : printer{p}, scanner{s} { }

    void print(Document &doc) override { printer.print(doc); }
    void scan(Document &doc) override { scanner.scan(doc); }
};    
  • 这给客户提供了灵活性,可以按照他们认为合适的方式组合抽象,并提供没有多余货物的实现。  
  • 正如 单一责任原则 中所解释的那样。你应该避免使用具有多重责任的类& 接口。因为它们经常变化,使你的软件难以维护。你应该尝试 s 根据角色将接口拼成多个接口

优点

=> 更快的编译

  • 如果你违反了ISP,即在接口中把方法塞在一起,而当方法签名改变时,你需要重新编译所有的派生类。这对一些编译语言来说是很重要的,比如 C++ ,它以 s 低编译率 而闻名。而另一种方式则是可以自行解释的。

=> 可重用性

  • 马丁 还提到, "胖接口"--带有额外无用方法 的接口--会导致类之间不经意的耦合。因此,一个有经验的开发者知道耦合是可重用性的祸根。  

=> 可维护性

  • ISP更普遍的好处是,通过避免不需要的依赖关系,系统变得
    • 更容易理解;
    • 更容易测试;
    • 更快改变。
  • 同样地,对于你的代码的读者来说,从类的声明行中了解你的类是做什么的,会比较困难。所以,如果dev只看到一个可能继承了其他接口的神接口,那很可能就不明显了。比较
  MyMachine : IMachine   

  MyMachine : IPrinter, IScanner, IFaxer   
  • ,后者告诉你很多,前者最多只能让你猜测。  

制作界面隔离原则友好型软件的标尺

  • 当你开始通过识别参与你的领域的主要角色来分解你的问题空间时,这个原则就自然而然地出现了。因此,这绝不是一个机械的行动。  
  • 向你自己提出一个问题可能会帮助你纠正你的设计。

我需要我正在使用的这个界面上的所有方法吗?(