有 OOP 委托时选择 class 方法
Choose class methods when there is OOP delegation
这是一个简单的场景,用于理解这个关于人和他们的房子的问题。
Person
可以改变他们 House
的颜色
我创建了这个 UML 图:
如上图所示:
- 一个
Person
可以改变他们的房屋颜色。为了证明这一点,我在 class Person
. 中使用 changeHouseColor()
方法
- 在
House
classchangeColor()
方法中是为了改变颜色属性。
我将使用 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.changeHouseColor()
方法。添加此方法是否违反封装?
- 如果
Person
class不需要这个方法,那么如何证明一个人可以改变他的房子颜色?
- 在 OOAD 中,人的行为不是必须作为方法包含在
person
class 中吗?
在这种情况下,两个基本选项是:
- 向
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()
方法来表明一个人可以改变房子的颜色。 Person
和 House
之间的简单关联表明 Person 可以调用 House
的任何 public 方法。
如果您提供一个 getHouse()
个人,或者如果您在协会的房子一端添加一个 public +myHouse
,您也会传达另一个 classes 可以访问 Person
的 House
简而言之,我的答案是:
- 不,没有违反封装。
Person
可以改变颜色,协会已经给了。
Person
的操作可能涉及调用其自己的操作,但许多其他操作也是可能的。
进一步说明:
要点1:封装在其形式化的描述中只是说了,不能直接使用其他Classes的属性。完全可以调用另一个 Class 的操作,也可以提供一个自己的操作,将某些东西委托给另一个 Class。正如其他答案所建议的那样,它是否是好的设计取决于您的特定领域。
要点 2: 当一个 Class 可以访问另一个 Class 的实例时,它可能会使用其所有 public 功能.在您的示例中,Person
和 House
之间存在关联。因此,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 中定义了许多其他操作,例如 ReadStructuralFeatureAction
或 CreateObjectAction
。它们不经常使用,因为在这种细节级别上建模没有多大意义。但是,我认为了解它们有助于更好地理解 Operations 和 Actions 是如何连接的。
这是一个简单的场景,用于理解这个关于人和他们的房子的问题。
Person
可以改变他们 House
我创建了这个 UML 图:
如上图所示:
- 一个
Person
可以改变他们的房屋颜色。为了证明这一点,我在 classPerson
. 中使用 - 在
House
classchangeColor()
方法中是为了改变颜色属性。
changeHouseColor()
方法
我将使用 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.changeHouseColor()
方法。添加此方法是否违反封装? - 如果
Person
class不需要这个方法,那么如何证明一个人可以改变他的房子颜色? - 在 OOAD 中,人的行为不是必须作为方法包含在
person
class 中吗?
在这种情况下,两个基本选项是:
- 向
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()
方法来表明一个人可以改变房子的颜色。 Person
和 House
之间的简单关联表明 Person 可以调用 House
的任何 public 方法。
如果您提供一个 getHouse()
个人,或者如果您在协会的房子一端添加一个 public +myHouse
,您也会传达另一个 classes 可以访问 Person
的 House
简而言之,我的答案是:
- 不,没有违反封装。
Person
可以改变颜色,协会已经给了。Person
的操作可能涉及调用其自己的操作,但许多其他操作也是可能的。
进一步说明:
要点1:封装在其形式化的描述中只是说了,不能直接使用其他Classes的属性。完全可以调用另一个 Class 的操作,也可以提供一个自己的操作,将某些东西委托给另一个 Class。正如其他答案所建议的那样,它是否是好的设计取决于您的特定领域。
要点 2: 当一个 Class 可以访问另一个 Class 的实例时,它可能会使用其所有 public 功能.在您的示例中,Person
和 House
之间存在关联。因此,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 中定义了许多其他操作,例如 ReadStructuralFeatureAction
或 CreateObjectAction
。它们不经常使用,因为在这种细节级别上建模没有多大意义。但是,我认为了解它们有助于更好地理解 Operations 和 Actions 是如何连接的。