所需接口
2015年10月12日
所需接口是指由交互的客户端定义的接口,它指定了供应商组件需要执行的操作,以便它可以在该交互中使用。
所需接口的一个很好的例子是通常称为“可比较”的接口。这种接口通常由排序函数需要。假设我有一组专辑,我想按标题对它们进行排序,但忽略“The”、“A”和“An”等冠词。我可以通过为任何排序函数实现所需接口来安排它们以这种方式排序。
在 Java 中,它看起来像这样。
class Album...
public class Album implements Comparable<Album> { private String title; public Album(String title) { this.title = title; } public String getTitle() { return title; } @Override public int compareTo(Album o) { return this.sortKey().compareTo(o.sortKey()); } private String sortKey() { return ignoreSortPrefixes(title).toLowerCase(); } private static String ignoreSortPrefixes(String arg) { final String[] prefixes = {"an", "a", "the"}; return Arrays.stream(prefixes) .map(s -> s + " ") .filter(s -> arg.toLowerCase().startsWith(s)) .findFirst() .map(s -> arg.substring(s.length(), arg.length())) .orElse(arg) ; }
在这种情况下,Comparable
是各种 Java 排序函数的所需接口。更复杂的示例可能具有更丰富的接口,其中定义了多个方法。
人们通常将接口视为供应商关于向客户端公开内容的决定。但所需接口是由客户端指定(并且通常定义)的。通过考虑客户端的要求,你通常可以获得更有用的接口——这将引导你思考角色接口。
使用适配器
如果我想将两个独立定义的模块连接在一起,就会出现一个常见的问题。即使我们获得了匹配的名称,我们也会遇到困难。
考虑一个具有任务所需接口的任务列表。
class TaskList...
private List<Task> tasks; private LocalDate deadline; public LocalDate latestStart() { return deadline.minusDays(tasks.stream().mapToInt(t -> t.shortestLength()).sum()); } }
interface Task…
int shortestLength();
假设我想将它与从另一个供应商获得的Activity
类集成。
class Activity…
public int shortestLength() { …
即使活动有一个方法,其签名恰好与所需接口的签名匹配,但我(正确地)不能创建活动的任务列表,因为类型定义不匹配。如果我无法修改活动类,我需要使用适配器。
public class ActivityAdapter implements Task { private Activity activity; public ActivityAdapter(Activity activity) { this.activity = activity; } @Override public int shortestLength() { return activity.shortestLength(); } }
在软件世界中,我们非常自由地使用术语“适配器”,但在这里我严格地按照四人帮书籍中的意义使用它。在这种用法中,适配器是一个将一个对象映射到另一个对象的所需接口的对象。
在这种情况下,如果我使用的是动态语言,则不需要适配器,但如果活动类使用的是具有不同签名的方法,则需要适配器。