包装器/德米特法则似乎是一种反模式
我一直在阅读“德米特定律”的内容,它(以及一般的纯“包装”类)似乎通常是反模式。考虑一个实现类:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
主要区别在于,在版本 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 howFoo
is exposed. Any change inFoo
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 theBar
abstraction, and notFoo
's. In your example, yourBar
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 ofFoo
, with no change at all.Suppose now Foo evolves, and require the user to call
foo.init()
before any call todoSomething
. With version 1, all users of Bar will need to see that Foo changed, and adapt their code. With version 2, onlyBar
has to be changed, itsdoSomething
callinginit
if needed. This leads to less bugs (only the author of abstractionBar
has to know and understand abstractionFoo
and less coupling between classes.这显然是一个人为的例子。在许多实际情况下,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 haveresetFluidSimulation()
keep working (without change to callingMethod) by calling_fluidDynamics.reset(true)
behind the scenes.问题是,callingMethod() 是否需要知道是否重新创建渲染表?
假设某些给定的
callingMethod()
执行需要或不需要重新创建渲染表。在这种情况下,您可以使用新方法扩展包装器。然后,您只需从适当的callingMethod()
实例中调用新方法即可。或者,重新创建的决定可能完全属于其他地方……
在这种情况下,
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 ofcallingMethod()
.Alternatively the decision to recreate may belong somewhere else entirely ...
... in whch case nothing in
callingMethod()
need change at all.