6

我一直在阅读这个“得墨忒耳法则”的东西,它(以及一般的纯“包装”类)似乎通常是反模式。考虑一个实现类:

class FluidSimulator {
    void reset() { /* ... */ }
}

现在考虑另一个类的两种不同实现:

class ScreenSpaceEffects1 {
    private FluidSimulator _fluidDynamics;
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; }
}

class ScreenSpaceEffects2 {
    private FluidSimulator _fluidDynamics;
    public void resetFluidSimulation() { _fluidDynamics.reset(); }
}

以及调用上述方法的方法:

callingMethod() {
   effects1.getFluidSimulator().reset(); // Version 1
   effects2.resetFluidSimulation();      // Version 2
}

乍一看,版本 2 似乎更简单一些,并且遵循“Demeter 规则”,隐藏 Foo 的实现等。但这将 FluidSimulator 中的任何更改与 ScreenSpaceEffects 联系在一起。例如,如果一个参数被添加到reset,那么我们有:

class FluidSimulator {
    void reset(bool recreateRenderTargets) { /* ... */ }
}

class ScreenSpaceEffects1 {
    private FluidSimulator _fluidDynamics;
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; }
}

class ScreenSpaceEffects2 {
    private FluidSimulator _fluidDynamics;
    public void resetFluidSimulation(bool recreateRenderTargets) { _fluidDynamics.reset(recreateRenderTargets); }
}

callingMethod() {
   effects1.getFluidSimulator().reset(false); // Version 1
   effects2.resetFluidSimulation(false);      // Version 2
}

在这两个版本中,callingMethod 都需要更改,但在版本 2 中,ScreenSpaceEffects需要更改。有人可以解释拥有包装器/外观的优势(适配器或包装外部 API 或暴露内部 API 除外)。

编辑:我遇到的许多真实例子之一,而不是一个微不足道的例子。

4

3 回答 3

14

主要区别在于,在版本 1 中,作为Bar抽象的提供者,您无法控制如何Foo公开。任何变化Foo都将暴露给您的客户,他们将不得不忍受。

使用版本 2,作为抽象的提供者Bar,您可以决定是否以及如何公开演进。它将仅取决于Bar抽象,而不是Foo's. 在您的示例中,您的Bar抽象可能已经知道将哪个整数作为参数传递,因此您将能够让您的用户透明地使用新版本的Foo,而无需进行任何更改。

假设现在 Foo 进化了,并且要求用户在foo.init()调用doSomething. 在版本 1 中,Bar 的所有用户都需要看到 Foo 发生了变化,并调整他们的代码。使用版本 2,只需Bar更改它,如果需要它的doSomething调用。init这会导致更少的错误(只有抽象的作者Bar必须知道和理解抽象Foo和更少的类之间的耦合。

于 2010-03-31T06:59:01.810 回答
2

这显然是一个人为的例子。在许多实际情况下,callingMethod(在现实生活中,可以有多个callingMethods)可以保持幸福地不知道 Foo.doSomething 已更改,因为 Bar 隔离了它。例如,如果我使用稳定的打印 API,我不必担心我的打印机固件会增加对光泽打印的支持。我现有的黑白打印代码继续工作。我想您会将其归类为“适配器”,我认为这比您暗示的要普遍得多。

你是对的,有时callingMethod也必须改变。但是如果正确使用得墨忒耳法则,这种情况只会很少发生,通常是为了利用新功能(与新界面不同)。

编辑:似乎很可能 callMethod 不关心渲染目标是否被重新创建(我假设这是一个性能与准确性的问题)。毕竟,“我们应该忘记小的效率,比如说大约 97% 的时间”(Knuth)。所以 ScreenSpaceEffects2 可以添加一个resetFluidSimulation(bool)方法,但通过在幕后resetFluidSimulation()调用来继续工作(不改变 callMethod) 。_fluidDynamics.reset(true)

于 2010-03-31T07:00:55.267 回答
2

问题是,是否callingMethod()需要知道是否重新创建渲染表?

假设某些给定的执行callingMethod()是否需要重新创建渲染表。在这种情况下,您可以使用新方法扩展包装器。然后你只需要从callingMethod().

class ScreenSpaceEffects2 {
    private FluidSimulator _fluidDynamics;
    public void resetFluidSimulation() { _fluidDynamics.reset(false); }
    public void resetFluidSimulationWithRecreate() { _fluidDynamics.reset(true); }
}

或者,重新创建的决定可能完全属于其他地方......

class ScreenSpaceEffects2 {
    private FluidSimulator _fluidDynamics;
    public void resetFluidSimulation() { 
             _fluidDynamics.reset( someRuleEngine.getRecreateRenderTables() ); }
}

...在这种情况下,根本callingMethod()不需要改变。

于 2010-03-31T11:14:07.250 回答