81

面向接口设计与角色接口

 5 years ago
source link: http://zhangyi.xyz/interface-oriented-design-and-role-interface/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

问题:在做项目的时候,是不是所有包含非静态方法的类,都要写一个接口?是因为这样的目的是为了解耦,然后通过DI注入实现吗?

回答:

这不是提取接口的根本原因。因为要做到解耦和DI,直接用类也是可以的,尤其Java的普通方法都是允许子类override的。此外,如果你的接口永远都只有一个实现类,并没有任何可能的需求变化,那么还有必要解耦吗?

所以说,不能死板的将类的方法提取接口,然后美其名曰为 面向接口设计 。我们不能误解“面向接口设计”原则,该原则所指的“接口”并非Java语言中的interface类型,而是指面向调用者对外暴露的接口,代表一种交互与协作,是对信息的隐藏和封装,而不是具体的interface类型。即使是普通的java方法仍然满足隐藏细节的原则,如果是public的,就可以认为该方法是“面向接口设计”中的接口,也就是说:不要针对实现细节编程,而是针对接口编程。

针对java中的interface类型,包含了两种含义:

  • 接口代表一种能力。例如在Java JDK中定义了很多这种接口,如 Runnable , Cloneable , Seriazable
  • 接口代表业务场景中与其他类型协作的角色。从语法特性看,就是对履行职责的角色的抽象。Martin Fowler将其称之为Role Interface(角色接口)。

例如邮件的收发业务。如果收邮件和发邮件可能会被用到不同的使用场景,换言之,这两个方法不会要求必须同时出现,那么就可以分别为其定义两个接口 EmailSender , EmailReceiver

public interface EmailSender {
    void send(Message message);
}

public interface EmailReciever {
    Message[] receive();
}

但是,我们可以定义一个类 EmailService 同时实现这两个接口,这就是所谓的“大对象、小接口”,又或者说是 接口隔离原则 的体现。

如果你先定义了一个类叫 EmailService ,然后因为你需要定义接口对其抽象,然后就简单地将这个类的所有公有方法都提取到抽象的接口中,这样设计的接口,被Martin Fowler称为Header Interface。欲知Role Interface与Header Interface,可以参考Martin Fowler的 这篇文章

问题:收发邮件被用到不同的使用场景,怎么理解?

回答:

比如说,在通知模块中,就只会用到发邮件这个方法,不会用到收邮件;在垃圾邮件识别器功能中,就只会用到收邮件这个功能。

问题:那这样做有什么好处?

回答:

好处包括:

  • 分解到不同的独立接口,你就可以针对不同变化独立演化,而不是让收与发同时变化。
  • 你可以有效地重用,而不是非得把包含所有方法的类都放进去。
  • 可以更好地提高编码可读性,以及类型验证,保证代码的健壮性。

例如说转账服务,一个是转出(credit),一个是转入(debit)。一种方法是定义一个接口 Account ,提供转入和转出的方法。定义的转账服务方法为:

public interface Account {
    void credit(Money amount);
    void debit(Money amount);
}

void transfer(Account source, Account destination);

这个方法只是从形参的名称体现了转出源与转入目标,这种依靠参数名称对转入和转出账户的顺序做约束是不可靠的。当我们在实现 transfer() 方法时,也有可能错误地调用 Account 的方法,导致潜在的bug。

如果我们基于转出上下文和转入上下文分别识别参与的角色,就可以提出两个接口 SourceDestination

public interface Source {
    void credit(Money amount);
}
public interface Destination {
    void debit(Money amount);
}

转账的服务方法就可以定义为:

void transfer(Source source, Destination destination);

你觉得哪个方法更安全、更可读?显然是后者,这就是建立角色接口的好处。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK