接口膨胀
我曾多次遇到过这样的情况:人们以为自己的架构做得很好,但结果却适得其反。接口臃肿(Interface Bloat)就是一个常见的后果。
有一次,当我与一位 Scrum Master 讨论在 PHP 中进行多态性时接口的重要性时,他告诉了我他曾经工作过的一个环境,在那里有一位工程师花了几个月的时间开发接口,并认为自己在做出色的架构工作。不幸的是,事实证明他并没有做出色的基础架构工作,而是犯了实施接口膨胀(Interface Bloat)的错误。
接口臃肿,顾名思义,就是接口过于臃肿。一个接口可以臃肿到几乎无法以其他方式实现一个类。接口应尽量少用;如果一个类只需要实现一次接口,那么你是否真的需要接口(实际上,没有人会编写这样的代码)。如果是这样,在这种情况下,你可能需要考虑避免使用接口。
接口不应作为测试单元功能的手段。在这种情况下,您确实应该使用单元测试,例如通过 PHPUnit。即便如此,单元测试应该测试一个单元是如何运作的,而不是被用作一种工具来确保没有人编辑你的代码。
因此,让我来介绍一下 Interface Bloat 的一种实现方式。让我们来看看 Pheanstalk 开源库中的 Pheanstalk
接口类(注意,为了让它更易读,我去掉了注释):
<?php
namespace Pheanstalk;
interface PheanstalkInterface
{
const DEFAULT_PORT = 11300;
const DEFAULT_DELAY = 0;
const DEFAULT_PRIORITY = 1024;
const DEFAULT_TTR = 60;
const DEFAULT_TUBE = 'default';
public function setConnection(Connection $connection);
public function getConnection();
public function bury($job, $priority = self::DEFAULT_PRIORITY);
public function delete($job);
public function ignore($tube);
public function kick($max);
public function kickJob($job);
public function listTubes();
public function listTubesWatched($askServer = false);
public function listTubeUsed($askServer = false);
public function pauseTube($tube, $delay);
public function resumeTube($tube);
public function peek($jobId);
public function peekReady($tube = null);
public function peekDelayed($tube = null);
public function peekBuried($tube = null);
public function put($data, $priority = self::DEFAULT_PRIORITY, $delay = self::DEFAULT_DELAY, $ttr = self::DEFAULT_TTR);
public function putInTube($tube, $data, $priority = self::DEFAULT_PRIORITY, $delay = self::DEFAULT_DELAY, $ttr = self::DEFAULT_TTR);
public function release($job, $priority = self::DEFAULT_PRIORITY, $delay = self::DEFAULT_DELAY);
public function reserve($timeout = null);
public function reserveFromTube($tube, $timeout = null);
public function statsJob($job);
public function statsTube($tube);
public function stats();
public function touch($job);
public function useTube($tube);
public function watch($tube);
public function watchOnly($tube);
}
请注意,在实现中甚至连常量也放进去了,而这正是你可能真的想要改变的东西。显然,这是一个类的接口,只能以一种方式实现,这使得接口毫无用处。
在编写面向对象代码时,接口提供了一个很好的结构;一旦实现,接口就能保证接口中的方法已在实现接口的类中实现。
然而,就像大多数好东西一样,接口也可能是一把双刃剑。曾经有人给了我一个非常幼稚的反对架构设计的论据;他们引用了他们以前的一位同事的例子,这位同事花了几个月的时间编写了非常详细的接口,并认为这是伟大的架构。事实上,他正在犯接口膨胀的错误。
接口不应该是一种强制实现的方式;事实上,有一些接口的例子会导致某些人面临无法以其他方式将接口实现到类中的问题。
接口不应包含成千上万个引用类内部操作的方法。它们应该是轻量级的,并被认为是一种保证方法,当查询某些东西时,它肯定存在。
有一种反模式被称为 瑞士军刀(或 "厨房水槽"),人们试图通过设计接口来满足类的所有可能用例。这会造成调试、文档和维护方面的困难。