包装器/德米特法则似乎是一种反模式

发布于 2024-08-27 17:42:54 字数 1540 浏览 11 评论 0原文

我一直在阅读“德米特定律”的内容,它(以及一般的纯“包装”类)似乎通常是反模式。考虑一个实现类:

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 联系起来。例如,如果添加一个参数来重置,那么我们有:

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 除外)。

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

I've been reading up on this "Law of Demeter" thing, and it (and pure "wrapper" classes in general) seem to generally be anti patterns. Consider an implementation class:

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

Now consider two different implementations of another class:

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

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

And the ways to call said methods:

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

At first blush, version 2 seems a bit simpler, and follows the "rule of Demeter", hide Foo's implementation, etc, etc. But this ties any changes in FluidSimulator to ScreenSpaceEffects. For example, if a parameter is added to reset, then we have:

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
}

In both versions, callingMethod needs to be changed, but in Version 2, ScreenSpaceEffects also needs to be changed. Can someone explain the advantage of having a wrapper/facade (with the exception of adapters or wrapping an external API or exposing an internal one).

EDIT: One of many real examples for which I ran into this rather than a trivial example.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

温暖的光 2024-09-03 17:42:54

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

在版本 2 中,作为抽象 Bar 的提供者,您可以决定是否以及如何公开演变。它将仅依赖于 Bar 抽象,而不依赖于 Foo 的抽象。在您的示例中,您的 Bar 抽象可能已经知道要传递哪个整数作为参数,因此您将能够让您的用户透明地使用新版本的 Foo,完全没有变化。

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

The main difference is that in version 1, as provider of the Bar abstraction, you have no control on how Foo is exposed. Any change in Foo will be exposed to your clients, and they will have to bear with it.

With version 2, as provider of abstraction Bar, you can decide if and how you want to expose the evolutions. It will depend only on the Bar abstraction, and not Foo's. In your example, your Bar abstraction may already know which integer to pass as argument, and thus you will be able to let your users transparently use the new version of Foo, with no change at all.

Suppose now Foo evolves, and require the user to call foo.init() before any call to doSomething. With version 1, all users of Bar will need to see that Foo changed, and adapt their code. With version 2, only Bar has to be changed, its doSomething calling init if needed. This leads to less bugs (only the author of abstraction Bar has to know and understand abstraction Foo and less coupling between classes.

疧_╮線 2024-09-03 17:42:54

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

你是对的,有时调用方法也必须改变。但是,当正确使用德米特定律时,这种情况只会很少发生,通常是为了利用新功能(与新界面不同)。

编辑:callingMethod 似乎很可能不关心是否重新创建渲染目标(我假设这是性能与准确性的问题)。毕竟,“我们应该忘记小效率,大约 97% 的时间”(Knuth)。因此,ScreenSpaceEffects2 可以添加一个 resetFluidSimulation(bool) 方法,但通过调用 _fluidDynamics.reset(true)< 让 resetFluidSimulation() 继续工作(无需更改 CallingMethod)< /code> 幕后。

This is obviously an artificial example. In many real cases, callingMethod (in real life, there can multiple callingMethods) can remain blissfully unaware that Foo.doSomething has changed, because Bar insulates it. For example, if I use a stable printing API, I don't have to be concerned about my printer's firmware adding support for glossy printing. My existing black-and-white printing code keeps on working. I suppose you would group this under "adapter", which I think it much more common than you imply.

You are right that sometimes callingMethod must be changed too. But when the Law of Demeter is used properly, this will only occur rarely, usually to take advantage of new functionality (as distinct from a new interface).

EDIT: It seems quite possible that callingMethod does not care whether render targets are recreated (I'm assuming this is a question of performance v. accuracy). After all, "We should forget about small efficiencies, say about 97% of the time" (Knuth). So ScreenSpaceEffects2 could add a resetFluidSimulation(bool) method, but have resetFluidSimulation() keep working (without change to callingMethod) by calling _fluidDynamics.reset(true) behind the scenes.

久伴你 2024-09-03 17:42:54

问题是,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() 中的任何内容都不需要更改。

The question is, does callingMethod() need to know whether to recreate the rendering tables?

Suppose some given execution of callingMethod() does or does not need to recreate the rendering tables. In that case you extend the wrapper with a new method. Then you just need to call the new method from just the appropriate instances of callingMethod().

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

Alternatively the decision to recreate may belong somewhere else entirely ...

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

... in whch case nothing in callingMethod() need change at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文