Head First Design Patterns 一书中的接口到接口关联
Interface to interface association in the book Head First Design Patterns
Head First Design Patterns 一书介绍了以下 UML 作为观察者模式的示例:
这张图让我印象深刻的是Subject
和Observer
接口之间的关联关系。据我了解 Java 接口,它们不能以这种方式实现“Has-a”关系。
当我查看几页后提供的实现示例时,我发现接口果然是普通的旧接口:
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers;
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
而是在具体WeatherData
和Display
中实现了关联关系classes:
public class WeatherData implements Subject {
private List<Observer> observers;
//more code
}
不符合UML,感觉有点不对。为什么不直接将 Subject
实现为抽象 class 呢? UML 规范是否形式化了将一个接口与另一个接口相关联的想法?
你的提问是对的。让我们看看为什么事情是这样的:
- 接口代表契约。在Java中,契约主要是一套方法。在 UML 中,它可以更广泛,例如是关于操作(方法)、属性(字段)、关联(不是 java 语言特性:有几种方法可以实现它们)或约束(前提条件、后置条件、不变量等)。
- 类 必须履行他们 realize/implement 接口的合同 。在 Java 中,编译器只会验证 class 是否实现了接口所需的方法以及正确的签名。开发人员永远不会期望更多,即 java class 的行为符合接口的文档。在 UML 中,您同样希望 class 为自己提供接口所承诺的所有功能(参见 here)。
- 如何执行规则? 在 Java 中,您必须明确:必须提供所有方法,否则会出现编译错误.如果您使用 TDD,您还会预见到一组测试来验证对象的行为是否符合接口的约定。在 UML 中,您可能会有歧义,因为您不必在图表中显示所有内容。因此,如果您不显示 class 特征或关联,并不意味着没有这样的东西:该东西可能在模型中但未在特定图表中显示。因此,如果 class 实现了一个接口,您可以假设它提供了元素,即使它们没有显示在图中。如果您使用这样一个模棱两可的图表来生成代码,代码将无法编译。
回到你的观察者:
- 图表告诉我们
Subject
与 Observer
有可导航的关联。在代码中,你不能在接口中实现甚至直接声明这种关系。但是,Subject
接口应允许注册和删除 Observers
,这一事实暗示了这种关联。
- 图表没有告诉我们
WheatherData
与 CurrentConditionDisplay
有可导航的关联。但原则上,他们应该,因为这些 classes 分别实现 Subject
和 Observer
。这使得图表不明确。然而,如果我们相信这些 classes 符合接口,我们可以在不显示额外关联的情况下生活,只是假设它们必须在这里。此外,严格的 UML 合规性要求在此处添加 3 个这样的关联,使图表更难阅读。
现在,正如您所说的那样,我们可以使用抽象 class 而不是接口来实现设计模式。但是由于 Java 不允许多重继承,这种方法将禁止主体或观察者处于其他 class 层次结构中。
一些额外的背景
首先,观察者模式——正如 Heads first Design Pattern 中的所有模式一样——起源于 GoF。有趣的是,GoF 早于 UML 和 Java;它的模式只使用 class 继承,如果需要使用多重继承。当书中提到“接口”时,它只是关于 classes 暴露的隐式接口,通常是抽象的 classes.
当 Java 出来时,转换所有这些设计模式是一个挑战,在接口(在 Java 意义上)和抽象 classes 之间做出明确的区分。这就是为什么您经常会在这些模式的 class 图表中发现多个变体。
正面领先也不例外。那本优秀书籍的目标是教授在编码中使用模式。因此,其图表的主要目的是传达设计意图,而不是教授高级 UML 的严格使用。因此,他们使用的图表有时可能在形式上模棱两可。总的来说,我认为他们做得很好。
Head First Design Patterns 一书介绍了以下 UML 作为观察者模式的示例:
这张图让我印象深刻的是Subject
和Observer
接口之间的关联关系。据我了解 Java 接口,它们不能以这种方式实现“Has-a”关系。
当我查看几页后提供的实现示例时,我发现接口果然是普通的旧接口:
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers;
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
而是在具体WeatherData
和Display
中实现了关联关系classes:
public class WeatherData implements Subject {
private List<Observer> observers;
//more code
}
不符合UML,感觉有点不对。为什么不直接将 Subject
实现为抽象 class 呢? UML 规范是否形式化了将一个接口与另一个接口相关联的想法?
你的提问是对的。让我们看看为什么事情是这样的:
- 接口代表契约。在Java中,契约主要是一套方法。在 UML 中,它可以更广泛,例如是关于操作(方法)、属性(字段)、关联(不是 java 语言特性:有几种方法可以实现它们)或约束(前提条件、后置条件、不变量等)。
- 类 必须履行他们 realize/implement 接口的合同 。在 Java 中,编译器只会验证 class 是否实现了接口所需的方法以及正确的签名。开发人员永远不会期望更多,即 java class 的行为符合接口的文档。在 UML 中,您同样希望 class 为自己提供接口所承诺的所有功能(参见 here)。
- 如何执行规则? 在 Java 中,您必须明确:必须提供所有方法,否则会出现编译错误.如果您使用 TDD,您还会预见到一组测试来验证对象的行为是否符合接口的约定。在 UML 中,您可能会有歧义,因为您不必在图表中显示所有内容。因此,如果您不显示 class 特征或关联,并不意味着没有这样的东西:该东西可能在模型中但未在特定图表中显示。因此,如果 class 实现了一个接口,您可以假设它提供了元素,即使它们没有显示在图中。如果您使用这样一个模棱两可的图表来生成代码,代码将无法编译。
回到你的观察者:
- 图表告诉我们
Subject
与Observer
有可导航的关联。在代码中,你不能在接口中实现甚至直接声明这种关系。但是,Subject
接口应允许注册和删除Observers
,这一事实暗示了这种关联。 - 图表没有告诉我们
WheatherData
与CurrentConditionDisplay
有可导航的关联。但原则上,他们应该,因为这些 classes 分别实现Subject
和Observer
。这使得图表不明确。然而,如果我们相信这些 classes 符合接口,我们可以在不显示额外关联的情况下生活,只是假设它们必须在这里。此外,严格的 UML 合规性要求在此处添加 3 个这样的关联,使图表更难阅读。
现在,正如您所说的那样,我们可以使用抽象 class 而不是接口来实现设计模式。但是由于 Java 不允许多重继承,这种方法将禁止主体或观察者处于其他 class 层次结构中。
一些额外的背景
首先,观察者模式——正如 Heads first Design Pattern 中的所有模式一样——起源于 GoF。有趣的是,GoF 早于 UML 和 Java;它的模式只使用 class 继承,如果需要使用多重继承。当书中提到“接口”时,它只是关于 classes 暴露的隐式接口,通常是抽象的 classes.
当 Java 出来时,转换所有这些设计模式是一个挑战,在接口(在 Java 意义上)和抽象 classes 之间做出明确的区分。这就是为什么您经常会在这些模式的 class 图表中发现多个变体。
正面领先也不例外。那本优秀书籍的目标是教授在编码中使用模式。因此,其图表的主要目的是传达设计意图,而不是教授高级 UML 的严格使用。因此,他们使用的图表有时可能在形式上模棱两可。总的来说,我认为他们做得很好。