14

在敏捷开发人员的基本技能中,在需求与能力接口中,第 12 章,我试图理解作者在本章末尾提到的应用分度法则的挑战所提出的主要解决方案。

为了使故事简短。

我们从以下研究案例开始:

public class City {
  public string name{};
  public City twinCity{};
  public Street[] streets{};
}
public class Street {
  public string name{};
  public House[] houses{};
}
public class House {
  public int number{};
  public Color color{};
}

城市网格模型

作者声明:

像这样的模型鼓励我们公开而不是封装。如果你的代码引用了一个特定的 City 实例,比如一个映射西雅图的实例,并且你想要 1374 Main Street 的房子的颜色,那么你可能会执行以下操作:

public Foo() {
  Color c = Seattle.streets()["Main"].
    houses()[1374].
    color();
}

如果将其作为一般做法进行,问题在于系统会在任何地方产生依赖关系,并且对该模型的任何部分的更改都会对这些依赖关系链的上下产生影响。这就是得墨忒耳法则的用武之地,它规定2“不要和陌生人说话”。这在对象系统中被形式化为函数/方法的得墨忒耳法则。对象 O 的方法 M 只能调用以下几种对象的方法:

  1. 奥氏
  2. M的参数
  3. 在 M 中实例化的任何对象
  4. O 的直接组件对象
  5. O 可访问的任何全局变量

并建议在应用 DEMTER 法则时,我们应该瞄准类似的东西

public Foo() {
   Color c = Seattle.ColorOfHouseInStreet("Main",1374);
}

并迅速从以下方面发出警告:

尽管最初这似乎是一个明智的策略,但它很快就会失控,因为任何给定实体的接口都可以预期提供与其相关的任何内容。这些接口往往会随着时间的推移而膨胀,事实上,给定 Glass 最终可能支持的公共方法的数量似乎几乎没有尽头。

然后在解释了耦合和依赖的问题后,他提到了通过服务的接口分离客户端及其服务的重要性,并且可能进一步将客户端的“需要接口”与“服务能力”分离接口”通过使用适配器作为理想但不一定实用的东西;

理想的解耦

他建议,为了解决这个问题,我们可以结合 DEMETER 法则和需求/能力分离,使用外观模式,如下所述:

从原始依赖

违反得墨忒耳定律

在应用 demeter 定律和需求/能力接口分离时,我们最初应该得到:

太复杂了

但是考虑到它是不切实际的,特别是从模拟的角度来看,我们可以用下面的外观来做一些更简单的事情:

LoD需求/能力分离 嘲笑

问题是我只是不明白这究竟是如何解决不违反得墨忒耳法则的问题。我认为它维护了原始客户和原始服务之间的法律。但它只是在 FACADE 内移动了违规行为。

4

3 回答 3

3

我猜你和作者之间存在“误传”。

第 1 步:你有这个:

需要接口

我希望您看到此时没有违反 LoD:每个具体类都依赖于其需求的抽象,因此仅调用该有限接口;加上对实体的依赖(例如Cityfor CityCapabilityAdapter),这是 LoD 所允许的。所以,目前没有违规。


第2步:然后你有这个:

需要门面

我认为作者在这里的意思是在概念上CityMapFacade依赖于所有 3 个实体,但在物理上并不直接与它们一起使用。相反,它通过 LoD 仪器与他们一起工作。例如,外观可能依赖于之前已经介绍过的那些适配器。所以,再一次,这里没有违反 LoD。

为了进一步阐明这种解释,请注意图中的抽象事物,它们是斜体的(UML 2.0)。因此,这CityMapFacade是一个负责解决其自身 LoD 问题的接口。这些可以通过已经证明的事实(例如适配器)来解决。图表上没有显示具体的解决方案,它只是谈论抽象。并且没有机会在外观的具体实现中争论 LoD 违规,因为作者已经证明他有工具可以通过引入适配器来解决违规问题。

于 2014-07-24T02:52:59.477 回答
3

我想说,根据您的描述,他只是以 CityMapFacade 的形式围绕 Cirty/Street/House 功能引入了一个黑盒组件。此外,该组件以接口 IClientNeeds 的形式遵循面向公众的接口。这种黑盒封装保护了更大的应用程序并使其可测试,因为城市/街道/房屋关系的复杂性没有暴露出来。似乎曾经的旧东西又是新的了 :) 这种黑盒组件式方法(因为没有更好的术语)已经存在了很长时间。它专注于创建具有良好定义接口的可重用、封装的功能组件。只要履行了面向公众的合同,客户就不会关心实施。该组件是可替换的、可升级的、可模拟的。在组件内,复杂性也降低了,因为它只负责应用程序的一个孤立子集。它通常是完全可单元测试的,以确保它正确实现其公共合约/接口。在我看来,作者已经对这一切进行了更深入的讨论,以简化他的解释。

于 2014-07-16T12:20:41.713 回答
2

我不知道您阅读的书,当我第一次阅读您的问题时,我认为那是“LoD发疯了”。我知道你问的是这个 Facade 的具体实现而不违反 LoD。

让我从我的第一个想法开始:如果我需要知道对象“House”的属性“颜色”,我认为向对象 House 询问其属性是绝对可以的。当然,我们不需要讨论由于您获得的依赖关系,从城市到街道再到房屋的循环链是一个非常糟糕的主意。

我只想实现一个getHouse方法

public class City {
  public House getHouse(street, number) {...}
}

该方法可能只是发现寻址的 Street 对象并询问具有给定编号且不会违反 LoD 的房屋。

当然,你最终会得到以下代码:

public Foo() {
  Color c = Seattle.getHouse("Main", 1374).color();
}

如果我们从字面上理解,这又违反了 LoD。

但无论如何,如果这是我需要房子颜色的唯一点,我会这样做,因为我看不到为单一用途创建 ServiceFacades 有任何好处。但是,如果不止一次需要它并且你真的不想破坏 LoD,这很容易做到,但我需要第二个视图才能看到这一点。您的 Facade 的实现将如下所示:

public class CityMapService
{
  public Color getColorOfHouse(City city, String nameOfStreet, int number)
  {
    Street street = city.getStreet(nameOfStreet);
    return getColorOfHouse(street, number);
  }

  public Color getColorOfHouse(Street street, int number)
  {
    House house = street.getHouse(number);
    return getColorOf(house);
  }

  public Color getColorOf(House house)
  {
    return house.getColor();
  }
}

没有任何方法会破坏 LoD。

这样做有意义吗?我会说是的。每个物体的内部结构都没有暴露出来,城市、街道和房屋如何连接的“更大”结构隐藏在你的立面后面。如果任何细节发生变化,您很可能只需要更改一个方法。并且嘲笑仍然非常容易和直接。

于 2014-07-24T14:03:05.527 回答