3
这是理解SOLID原则,关于接口隔离原则如何帮助我们创建简单的抽象接口,并使客户端代与接口之间存在的更少的依赖关系。

接口隔离原则是什么

Clients should not be forced to depend on methods that they do not use.

客户端代码不应当被迫依赖于它们不需要的方法。

这个原则本身与单一职责原则关系十分紧密,它意味着当你在定义你的抽象层代码时,不应当在客户端代码在实现抽象逻辑时,暴露一些客户端代码不需要使用或者关心的方法。

进一步说明的话,就是当你有意地在抽象层中暴露的方法时,这意味着所有实现这些抽象逻辑的客户端代码都必须要实现所有的抽象方法,尽管这些方法并不一定都对客户端代码有意义。

将你的接口的保持精简和小颗粒度,并且不要在它们中间增加无用的抽象方法,当你在对新的抽象接口进行命名时,你就会拥有更好的选择,因为你已有了若干小颗粒的命名类型。这样做的意义在于当你在需要提供一个更加大颗粒度的抽象接口时,你可以拥有足够的灵活性来将已有的小颗粒度接口进行组合。

如何实践接口隔离原则

这个例子是关于一个ATM用户界面的抽象接口,这个接口会处理诸如存款请求、取款请求等逻辑,从这个例子中我们会了解到,我们如何对这个接口进行隔离,使其进一步划分为多个独立的、更加具体的若干接口。

首先我们应当有一个工具函数库接口,这个接口会描述我们想要暴露的关于byte操作逻辑的方法,让我们创建这样一个接口,如下

type ByteUtils interface {
    Read(b []byte) (n int, err error) // Read into buffer
    Write(b []byte)(n int, err error) // Write into buffer
    Trim(b []byte, exclusions string)[]byte // Trim buffer by removing bytes from the exclusion chars
}

它可以正常工作一段时间,但是很快我们就会发现以下两个问题:

  • 它的命名ByteUtils太过于通用,如果我们仅通过命名本身,基本无法获取任何具体的信息
  • 当使用它时,会有一些古怪的感觉,因为当你根据不同的优化场景来按不同逻辑实现trim方法时,你所实现的readwrite几乎没什么差别,但是你却需要重复地实现它们,同时在某些不需要读或者写的场景,仍然需要实现它们。

所以它虽然能够正常工作,但是却不够好。

我们可以通过创建三个更精简、更具体的接口来替代原先通用的接口:

type Reader interface {
    Read(b []byte) (n int, err error) 
}
type Writer interface {
    Write(b []byte)(n int, err error) 
}
type Trimmer interface {
    Trim(b []byte, exclusions string)[]byte 
}

这种颗粒度比较细的接口也可以称为角色接口,因为它们更易于重构和改变,甚至对于已经定义好的角色和目的也可以很容易的进行重新部署和定义。

在这三个基础上,我们可以通过组合它们来获取一个更有关联性的接口列表,比如:

type ReadWriter interface {
    Reader
    Writer 
}
type TrimReader interface {
    Trimmer
    Reader
}

这意味客户端代码拥有了可以根据它们各自的需求来组合抽象层接口的灵活性,这样就会避免在实现抽象接口时不必要的麻烦(比如必须要实现某些无用的方法),比如上面的TrimReader的实现并未包含多余的Write方法的声明。

总结

正如你所看到的,通用的接口往往会无意识的将自己和类的实现耦合在了一起,所以你应当尽量的避免这种情况的发生。在设计接口时,你应当时刻提醒自己,我是否需要使用所有在接口中声明的方法呢?如果不是的话,将接口细分为更多个更精简、更具体的接口。

正如甘地曾经说过:

你的行动决定你的习惯,你的习惯决定你的价值,你的价值会决定你的命运。

如果在架构中,你每次都会经过仔细思考,会按照好的模式来进行设计,它将会成为一种习惯,自然慢慢会转变为你的价值或者原则,最终则会成为你的命运,比如成为了一个始终给予完善解决方案的软件架构师。

我的观点是,始终通过挑战自己来变的更好,在某些时刻,你可能会遇到问题,但是往往你可能已经拥有了答案。

Happy coding!

译者注

对于接口隔离原则的理解,我一直觉的它本身其实是单一职责原则的一个扩展,但是它们之间也有细微的不同:

  • 单一职责原则往往面向实现层,比如具体的类或者某个方法
  • 接口隔离原则往往面向抽象层,比如一些抽象类或者抽象方法

所以将两个原则结合起来看的话,可以很容器得到当时提出这两个原则的人的意图,那就是一定要时刻保持简单

在实际工作中,我深知保持简单是一件十分困难的事情,因为工程师本身的使命便是解决问题,而问题往往充满了未知性,而未知性往往代表着改变,这还没有考虑到在项目实施过程中,产品经理天马行空的设计思路,客户们五花八门的需求等等。在这些外界条件下,我们的代码往往会变得复杂无比,充满了各种反模式和冗余代码,最终会使自己陷入无尽的bug修复和维护工作中,怎么还会有时间进行自我提升呢?

所以,为了能够按时下班,为了能够及早回家,为了能够让我们的拥有更多的时间来提升自己和陪伴家人,在软件设计之初,尽可能地针对将来所面临的改变,在设计层面降低软件抽象模块间的耦合程度,在项目实施时,提高每个具体实现模块内部的内聚程度,同时使它们保持简单,这样便是一个好的开始。

关注公众号 全栈101,只谈技术,不谈人生

clipboard.png


littlelyon
7.9k 声望1.5k 粉丝

华丽的跌倒胜过无谓的徘徊