是否有“只问您需要”的界面原则?


9

我已经成长为使用一种设计和使用接口的原理,该原理基本上说:“只问您需要什么”。

例如,如果我有一堆可以删除的类型,我将创建一个Deletable接口:

interface Deletable {
   void delete();
}

然后,我可以编写一个通用类:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

在代码的其他地方,我总是要求最小的责任来满足客户代码的需求。因此,如果我只需要删除一个File,我仍然会要求一个Deletable,而不是一个File

这个原则是常识并且已经有公认的名称吗?有争议吗?在教科书中讨论过吗?


1
耦合松散?还是狭窄的接口?
tdammers

Answers:


16

我确实相信这是指Robert Martin所说的接口隔离原则。接口分为小而简洁的接口,以便消费者(客户)只需了解他们感兴趣的方法。您可以在SOLID上查看更多信息。


4

为了扩展Vadim的很好答案,我将用“不,不是真的”回答“有争议的”问题。

通常,通过减少所涉及的各种对象的“更改原因”的总数,接口隔离是一件好事。核心原理是,当必须更改具有多个方法的接口时,比如说要向一个接口方法中添加一个参数,那么该接口的所有使用者至少必须重新编译,即使他们没有使用更改的方法也是如此。。“但这只是重新编译!”,我听到你说;可能是正确的,但请记住,通常,无论对二进制文件的更改有多重要,重新编译的所有内容都必须作为软件补丁的一部分推出。这些规则最初是在90年代初概念化的,当时普通的台式机工作站的功能不如口袋里的电话强大,14.4k波特拨号非常简单,而3.5“ 1.44MB”软盘“是主要的可移动媒体。即使在当前的3G / 4G时代,无线互联网用户也经常会制定有限制的数据计划,因此在发布升级时,必须下载的二进制文件越少越好。

但是,与所有好的想法一样,如果实现不当,接口隔离可能会变糟。首先,有一种可能是,通过隔离接口,同时保持实现这些接口的对象(实现依赖关系)相对不变,您可能最终得到“ Hydra”,这是“ God Object”反模式的相对对象,狭窄的接口将对象的无所不知,无所不能的本质隐藏起来。最终,您会遇到一个多头怪物,其维护难度至少与上帝对象一样,再加上维护其所有接口的开销。没有太多的接口不应该超过,但是在单个对象上实现的每个接口都应通过回答“该接口对对象有贡献”这个问题作为开头。

其次,尽管SRP可能告诉您,每种方法的接口可能不是必需的。您可能会得到“馄饨代码”;如此大的一口大小的块,以至于很难找出确切的实际发生位置。如果该接口的所有当前用户都需要使用这两种方法,则也无需使用该方法拆分接口。即使其中一个依赖类仅需要两种方法之一,但如果在概念上它们的方法具有很高的内聚性,则不拆分接口也是可以接受的(很好的例子是“反义方法”,它们彼此完全相反)。

接口隔离应基于依赖于接口的类:

  • 如果只有一个类依赖于该接口,请不要隔离。如果该类不使用一个或多个接口方法,并且它是该接口的唯一使用者,那么很可能您不应该一开始就公开这些方法。

  • 如果有多个依赖于该接口的类,并且所有依赖项都使用该接口的所有方法,请不要隔离;如果必须更改接口(以添加方法或更改签名),则无论您是否隔离,当前所有使用者都将受到更改的影响(尽管如果要添加至少一个依赖者不需要的方法,请考虑请谨慎考虑是否应将更改作为新接口实现(可能继承自现有接口)。

  • 如果有多个类依赖于该接口,并且它们使用所有相同的方法,则它是隔离的候选对象。查看界面的“一致性”;所有方法是否都可以实现一个非常特定的编程目标?如果您可以为接口(及其实现者)确定一个以上的核心目的,请考虑沿这些方向拆分接口以创建较小的接口,并减少“更改理由”。


还值得注意的是,如果使用一种OOP语言/系统可以允许代码指定接口的精确组合,则接口隔离可能会很好,而且花哨的花哨,但至少在.NET中,它们可能会引起一些严重的麻烦,因为没有很好的解决办法。指定“实现IFoo和IBar的东西,但可能没有其他共同点的东西”的集合的方式。
supercat 2014年

可以使用包括多个接口的实现在内的标准来定义泛型类型参数,但是您说对了,因为需要静态类型的表达式通常不能支持指定多个。如果需要静态类型同时实现IFoo和IBar,并且您可以控制这两个接口,那么最好实现IBaz : IFoo, IBar并要求使用它。
KeithS 2014年

如果客户端代码可能需要用作IFoo和的东西IBar,定义一个复合对象IFooBar可能是一个好主意,但是如果对接口进行了精细的划分,最终很容易需要数十种不同的接口类型。请考虑以下集合可能具有的功能:枚举,报告计数,读取第n个元素,写入第n个元素,在第n个元素之前插入,删除第n个元素,新建项(扩大集合并返回新空间的索引)和添加。九种方法:ECRWIDNA。我大概可以描述数十种自然支持许多不同组合的类型。
2014年

例如,阵列将支持ECRW。阵列列表将支持ECRWIDNA。线程安全列表可能支持ECRWNA [尽管A通常仅对预填充列表有用。只读数组包装器可能支持ECR。协变量列表接口可以支持ECRD。非通用接口可以提供类型安全的支持C或CD。如果选择交换,则某些类型可以支持CS,但不支持D(例如,数组),而其他类型则支持CDS。试图为每种必要的能力组合定义不同的界面类型将是一场噩梦。
2014年

现在想象一下,有人希望能够用一个对象包装一个集合,该对象可以完成集合可以做的所有事情,但记录每个事务。一个需要多少个包装器?如果所有集合都从一个公共接口继承而来,该接口包含用于标识其功能的属性,则只需一个包装即可。但是,如果所有接口都不同,则将需要数十个接口。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.