该接口分离原则说:
不应强迫任何客户端依赖其不使用的方法。ISP将非常大的接口拆分为更小和更具体的接口,以便客户端仅需了解他们感兴趣的方法。
这里有一些未解决的问题。一种是:
多么小?
你说:
目前,我通过根据模块客户端的需求划分模块的命名空间来解决此问题。
我称这种手动鸭子打字。您构建的界面仅显示客户端需要的内容。接口隔离的原理不仅仅是简单的鸭子输入。
但是,ISP也不只是简单地呼吁可以重用的“一致”角色接口。没有“连贯的”角色界面设计可以完美地防止添加具有其自身角色需求的新客户端。
ISP是一种使客户端免受服务更改影响的方法。它的目的是使更改时构建速度更快。当然,它还有其他好处,例如不破坏客户,但这是重点。如果我要更改服务count()
功能签名,那么不用的客户端count()
不需要编辑和重新编译就很好了。
这就是为什么我关心接口隔离原则。我认为信念并不重要。它解决了一个真正的问题。
因此,应采用的方式应该可以为您解决问题。应用ISP并没有死胡同的死法,仅通过正确的必要变更示例就可以击败它。您应该查看系统的变化方式,并做出选择以使事情平静下来。让我们探索选项。
首先问自己:现在很难对服务接口进行更改吗?如果没有,请出去玩直到你冷静下来。这不是理智的练习。请确保治愈不会比疾病差。
如果许多客户端使用相同的功能子集,则说明“连贯”的可重用接口。该子集可能围绕一种想法,我们可以将其视为服务提供给客户的角色。这很有效。这并不总是有效。
如果许多客户端使用不同的功能子集,则该客户端实际上可能通过多个角色使用该服务。可以,但是很难看到角色。找到它们并尝试将它们分开。这可能使我们回到第一种情况。客户端仅通过多个接口使用该服务。请不要开始投放服务。如果这意味着不止一次将服务传递给客户端。那行得通,但是让我怀疑服务是否不是一个需要解决的大难题。
如果许多客户端使用不同的子集,但您甚至看不到角色,甚至不允许客户端使用多个子集,那么您没有比鸭子输入更好的设计接口的了。这种设计界面的方式可确保即使客户端未使用的功能也不会暴露给客户端,但几乎可以确保添加新客户端将始终涉及添加新接口,而服务实现不需要知道关于它,聚合角色接口的接口将。我们只是用一种痛苦换了另一种痛苦。
如果许多客户端使用不同的子集(重叠),则需要添加新的客户端,这些子集将需要不可预测的子集,并且您不愿意拆分服务,然后考虑使用功能更强大的解决方案。由于前两个选项不起作用,并且您实际上处在一个糟糕的地方,没有任何模式可循,并且即将进行更多更改,因此请考虑为每个功能提供自己的接口。结束此处并不意味着ISP已失败。如果失败了,那就是面向对象的范例。单一方法接口在极端情况下遵循ISP。这是相当多的键盘输入,但是您可能会发现这突然使接口可重用。同样,请确保没有
事实证明,它们确实可以变得很小。
我已将此问题视为在最极端的情况下应用ISP的挑战。但是请记住,最好避免极端情况。在经过深思熟虑的应用其他SOLID原理的设计中,这些问题通常几乎不会发生或没有关系。
另一个未解决的问题是:
谁拥有这些接口?
我一遍又一遍地看到以我所谓的“图书馆”心态设计的界面。我们都对猴子看到猴子做编码感到内gui,因为您只是在做某事,因为那是您看到的方式。我们对接口感到同罪。
当我查看为库中的类设计的接口时,我曾经想过:哦,这些人都是专家。这必须是建立界面的正确方法。我无法理解的是,图书馆边界有其自身的需求和问题。一方面,图书馆完全不了解其客户的设计。并非每个边界都是相同的。有时甚至同一边界也有不同的穿越边界的方法。
这是查看界面设计的两种简单方法:
那谁是对的?
考虑插件:
谁拥有这里的接口?客户?服务?
证明两者。
这里的颜色是图层。红色层(右)不应了解绿色层(左)。可以更改或替换绿色层而无需触摸红色层。这样,任何绿色层都可以插入红色层。
我喜欢知道应该知道什么,不应该知道什么。对我而言,“最了解什么?”是唯一最重要的体系结构问题。
让我们澄清一些词汇:
[Client] --> [Interface] <|-- [Service]
----- Flow ----- of ----- control ---->
客户就是使用的东西。
服务是所使用的东西。
Interactor
碰巧都是。
ISP说,断开客户端的接口。很好,让我们在这里应用它:
Presenter
(服务)不应该指示Output Port <I>
接口。该接口应缩小到所需的范围Interactor
(在此充当客户端)。这意味着关于,Interactor
并且要遵循ISP 的接口KNOWS 必须随之更改。这很好。
Interactor
(在此充当服务)不应该指示Input Port <I>
接口。该接口应缩小为Controller
(客户端)所需的接口。这意味着关于,Controller
并且要遵循ISP 的接口KNOWS 必须随之更改。这是不对的。
第二个是不好的,因为红色层不应该知道绿色层。ISP错了吗?好吧 没有原则是绝对的。在这种情况下,喜欢界面来显示服务可以执行的所有操作的傻瓜被证明是正确的。
至少,如果Interactor
除了该用例需要之外没有做任何其他事情,那么它们是正确的。如果这些Interactor
事情适用于其他用例,则没有理由对此Input Port <I>
有所了解。不知道为什么Interactor
不能只关注一个用例,所以这不是问题,但是事情还是会发生。
但是input port <I>
接口根本无法将自己作为Controller
客户端的奴隶,而这是一个真正的插件。这是一个“图书馆”边界。在发布红色层数年之后,完全不同的编程商店可能正在编写绿色层。
如果您越过“库”边界,即使您不拥有另一侧的接口,但仍感觉需要应用ISP,您将必须找到一种方法来缩小接口而不更改接口。
实现这一目标的一种方法是适配器。将其放在类似Controler
和的客户端之间Input Port <I>
。该适配器接受Interactor
作为Input Port <I>
和代表它的工作吧。但是,它仅Controller
通过角色接口或绿色层所拥有的接口公开客户想要的东西。适配器本身并不遵循ISP,而是允许使用更复杂的类Controller
来享受ISP。如果适配器的数量少于Controller
使用它们的客户端的数量,并且当您处于跨越库边界的异常情况下并且尽管已发布但库不会停止更改时,这很有用。看着你的Firefox。现在,这些更改只会破坏您的适配器。
那么这是什么意思?老实说,这意味着您没有提供足够的信息让我告诉您您应该怎么做。我不知道是否不遵循ISP会导致您出现问题。我不知道遵循它是否最终不会给您带来更多问题。
我知道您正在寻找简单的指导原则。ISP试图做到这一点。但这还没有说很多。我相信。是的,在没有充分理由的情况下,请不要强迫客户依赖他们不使用的方法!
如果您有充分的理由(例如,设计一些可以接受插件的东西),请注意没有遵循ISP原因的问题(在不破坏客户的情况下很难更改)以及减轻它们的方法(保持Interactor
或至少Input Port <I>
专注于一个稳定的解决方案)用例)。