简介

接口隔离原则规定,不应该强制接口的实现依赖于它不使用的方法。你是否曾被迫去实现一些你用不到的接口方法?如果答案是肯定的,那么你可能会在实现里创建一个空方法。这就是一个使用了违背接口隔离原则的接口的例子。

在实际操作中,这个原则要求接口必须粒度很细,且专注于一个领域。听起来很耳熟?记住,所有五个「SOLID」原则都是相关的,也就是说违背了其中一个原则,通常意味着也违背了其他的原则。当你违背了接口隔离原则,肯定也违背了单一职责原则。

与其保有一个包含不是所有实现都需要的方法的「臃肿」接口,不如将其拆分成多个更细粒度的接口,然后各自按需独立实现。这样将臃肿接口拆分成细粒度、功能集中的接口后,调用方也可以依赖更小的接口,而不必为我们不需要的功能买单。

实践

为了说明该原则,我们来看一个会话处理类库的例子。实际上,我们将要考察 PHP 自带的 SessionHandlerInterface。下面是该接口定义的方法,它们是从 PHP 5.4 版才开始有的:

  1. interface SessionHandlerInterface {
  2. public function close();
  3. public function destroy($sessionId);
  4. public function gc($maxLifetime);
  5. public function open($savePath, $name);
  6. public function read($sesssionId);
  7. public function write($sessionId, $sessionData);
  8. }

现在,我们已经知道接口里面都定义了什么方法了,我们打算基于 Memcached 来实现它。Memcached 实现类需要实现这个接口里的所有方法吗?不,里面一半的方法对于 Memcached 来说都是不需要实现的!

因为 Memcached 会自动清除存储的过期数据,我们不需要实现 gc 方法。我们也不需要实现 openclose 方法。所以,我们不得不在实现类中将相应的实现定义为空方法。为了解决在这个问题,我们来定义一个小巧的、专门用来回收过期 Session 数据的接口:

  1. interface GarbageCollectorInterface
  2. {
  3. public function gc($maxLifetime);
  4. }

现在我们有了一个更细粒度的接口,功能单一而专注。调用方只需要依赖它就可以了,而不必去依赖整个会话处理器。

为了更深入理解该原则,我们用另一个例子来强化理解。想象我们有一个名为Contact 的 Eloquent 模型类,定义成这样:

  1. class Contact extends Eloquent
  2. {
  3. public function getNameAttribute()
  4. {
  5. return $this->attributes['name'];
  6. }
  7. public function getEmailAttribute()
  8. {
  9. return $this->attributes['email'];
  10. }
  11. }

现在,我们再假设应用里还有一个叫 PasswordReminder 的类来负责给用户发送密码找回邮件。下面是 PasswordReminder 类一种可能的定义方式:

  1. class PasswordReminder
  2. {
  3. public function remind(Contact $contact, $view)
  4. {
  5. // Send password reminder e-mail...
  6. }
  7. }

你可能注意到了,PasswordReminder 依赖 Contact 类,也就是依赖 Eloquent ORM。将密码找回系统和一个特定的 ORM 实现耦合到一起实在是没必要,也是不可取的。切断这个依赖后,就可以自由地改变后台存储机制或者 ORM,同时不会对密码找回组件造成影响。重申一遍,违背了「SOLID」原则的任何一条,都会导致调用方了解更多应用其它部分的它不改知道的实现细节。

要切断这种依赖,需要创建一个 RemindableInterface 接口。事实上,Laravel 已经自带了这个接口,并且默认被 User 模型类实现:

  1. interface RemindableInterface
  2. {
  3. public function getReminderEmail();
  4. }

接口创建好了之后,我们就可以在模型类中实现它:

  1. class Contact extends Eloquent implements RemindableInterface
  2. {
  3. public function getReminderEmail()
  4. {
  5. return $this->email;
  6. }
  7. }

这样,我们就可以在 PasswordReminder 里面依赖这个小巧且专注的接口了:

  1. class PasswordReminder
  2. {
  3. public function remind(RemindableInterface $remindable, $view)
  4. {
  5. // Send password reminder e-mail...
  6. }
  7. }

通过这样的改动之后,我们已经移除了密码找回组件里不必要的依赖,并且使它足够灵活,可以使用任何实现了 RemindableInterface 接口的类或 ORM。这其实正是 Laravel 密码找回组件如何保持与数据库和 ORM 无关的秘诀!

我们再一次发现了让类过多地了解应用程序实现细节的缺陷。通过仔细观察我们在类中暴露的实现细节,我们就可以遵守所有 SOLID 原则。