有 OOP 委托时选择 class 方法

Choose class methods when there is OOP delegation

这是一个简单的场景,用于理解这个关于人和他们的房子的问题。

Person 可以改变他们 House

的颜色

我创建了这个 UML 图:

如上图所示:

我将使用 java 代码实现上述场景,如下所示。请注意此代码为伪代码,因此语法可能有误。

class Person {
    private House house;

    Person(House house) {
        this.house = house;
    }

    public void changeHouseColor() {
        house.changeColor(); // Delegation call
    }
}
class House {
    private String color;

    public string getColor() {
        return color;
    }

    public void changeColor(String color) {
        this.color = color;
    }
}

我想知道:

在这种情况下,两个基本选项是:

  • Person添加一个getHouse()方法,这需要客户端代码观察到这一点并适当地使用House的接口,或者
  • 正如您所做的那样,使用 Person 上的额外方法隐藏委托 (House)。

这是 Fowler 的重构中讨论的经典设计权衡:改进现有代码的设计 作为 Hide Delegate 重构。要回答你的问题,除了你提到的文档好处之外,隐藏委托实际上 增加 封装:

If the delegate changes its interface, changes propagate to all the clients of the server that use the delegate. I can remove this dependency by placing a simple delegating method on the server that hides the delegate. Then any changes I make to the delegate propagate only to the server and not to the clients.

总的来说,person.changeHouseColor(color)person.getHouse().changeColor(color)都没有问题。哪种方法更好取决于您的 domain.

我们可以说 person.getHouse().changeColor(color) 违反了 Law of Demeter (LOD) 或最少知识原则。这个想法是只了解 "closely" 相关的 classes。 类 拥有 Person 的实例不应依赖于其内部结构,而应仅调用 Person 的方法:person.changeHouseColor(color).

这种方法既有优点也有缺点。主要优点是能够更改 classes (Person class) 的内部结构,而无需修改其客户端。

绝对不违反封装范式。限制对某些对象组件的直接访问甚至可以视为更好的封装。

缺点是需要编写许多包装方法来传播对委托的调用。

但是如果你想表达只有人可以改变改变一个House颜色比模型可能不理想。因为任何有权访问 House 实例的 class 都可以直接调用 house.changeColor(color) 而无需 Person 实例。至少在创建它的方法中会有一个 House 实例:

House house = new House(Color.WHITE);
Person person = new Person(house);
person.changeHouseColor(Color.ORANGE); //OK
house.changeColor(Color.WHITE); //should we allow it?

这方面很大程度上取决于域。在您的域中识别 Aggregates 很重要。

简而言之

从根本上说,这个问题没有普遍的对错。这完全取决于您希望在 classes 的协作中承担的责任。但在大多数情况下,不要!

优点 - 何时执行此操作?

如果 Person 改变颜色是唯一的责任,而其他人不应该关心 House,那么您的设计是有道理的。正如 已经很好地解释的那样,它具有隐藏委托的优点。

但这并不是在 class 之间分配职责的唯一方式,并且有很多理由让设计更加开放,例如: 如果 Person 不自己粉刷房子,而是请 Painer 代她粉刷怎么办?如果假设村长有权决定颜色的变化,并派他的油漆工穿过村庄怎么办?

缺点 - 尽可能避免!

你的设计有很大的缺点。它与 Single Responsibility Principle (SRP) 有关:

A class should have only one reason to change

添加changeHouseColor意味着Person有不止一个原因要改变:首先它可能随着Person本身的概念而改变。但它可能还必须随着 House 变化的结果而改变。例如,如果明天 House 的接口演变为需要 changeColor 的附加厚度参数,不仅您的 Person 实现会发生变化(在委托调用中提供附加参数),但 Person 界面也必须更改,以便也提供厚度。因此,House 中的一个小变化将传播到使用 Person 的所有 class。隐藏的耦合很快就会变成 技术债

如果您开始复制每个可以远程访问它的 class 中的每个方法,您将进一步面临接口中的组合扩展,这将迅速冻结任何接口的演变,因为存在以下风险:改变传播。

结论

关注点分离应该驱动您的设计。如果Person的概念独立于House的概念,不要不必要地耦合它们。只有在非常需要时才这样做(例如,如果房子是私人的,没有人必须知道它)。

此外,在 UML 中不需要添加 changeHouseColor() 方法来表明一个人可以改变房子的颜色。 PersonHouse 之间的简单关联表明 Person 可以调用 House 的任何 public 方法。

如果您提供一个 getHouse() 个人,或者如果您在协会的房子一端添加一个 public +myHouse,您也会传达另一个 classes 可以访问 PersonHouse

简而言之,我的答案是:

  1. 不,没有违反封装。
  2. Person可以改变颜色,协会已经给了。
  3. Person 的操作可能涉及调用其自己的操作,但许多其他操作也是可能的。

进一步说明:

要点1:封装在其形式化的描述中只是说了,不能直接使用其他Classes的属性。完全可以调用另一个 Class 的操作,也可以提供一个自己的操作,将某些东西委托给另一个 Class。正如其他答案所建议的那样,它是否是好的设计取决于您的特定领域。

要点 2: 当一个 Class 可以访问另一个 Class 的实例时,它可能会使用其所有 public 功能.在您的示例中,PersonHouse 之间存在关联。因此,Person 可以调用 changeColor,但不是必须这样做。在一个更完整的模型中,House 可能有更多的特性,但并非所有特性都可能被 Person 使用。在这种情况下,定义客户端特定接口很有用,它只包含 Person Class 所需的功能。 Association 将被定向到此接口,而该接口又由 House Class 实现。 Person Class 在技术上仍然不需要使用界面的所有功能。但是,我会将接口关联视为建模者意图的强烈指示,最终会使用所有功能。

第 3 点: 在 OOAD Activity 中,图通常用于 分析 用例。在这种情况下,操作未连接到特定的 Classes,因此在此步骤中未定义任何操作。

在完整的设计 模型中,Person Class 的行为将以某种方式指定。它可能带有 Activity。在这个 Activity 中可以有很多动作。一个可能是 CallOperationAction,调用 changeColor。这将是 Person Class 如何使用 House Class 的明确说明。它还可以调用自己的操作 changeHouseColor,而后者又可以用 Activity 指定。

UML 中定义了许多其他操作,例如 ReadStructuralFeatureActionCreateObjectAction。它们不经常使用,因为在这种细节级别上建模没有多大意义。但是,我认为了解它们有助于更好地理解 Operations 和 Actions 是如何连接的。