方法链接与封装


17

方法链接与“单访问点”方法存在经典的OOP问题:

main.getA().getB().getC().transmogrify(x, y)

main.getA().transmogrifyMyC(x, y)

第一个优点似乎是,每个类仅负责较小的一组操作,并使所有事情都变得更加模块化 -向C添加方法不需要A,B或C花费任何精力来公开它。

当然,不利的一面是较弱的封装,第二种代码可以解决此问题。现在,A可以控制通过它的每个方法,并且可以根据需要将其委派给它的字段。

我意识到没有单一的解决方案,它当然取决于上下文,但是我真的很想听听有关两种样式之间其他重要区别的意见,在什么情况下我应该选择其中任何一种-因为现在,当我尝试设计一些代码,我觉得我只是在不使用参数来决定一种方法。

Answers:


25

我认为《德米特耳法则》为此提供了重要的指导原则(其优缺点通常应根据具体情况进行衡量)。

遵循Demeter定律的优势在于,最终的软件倾向于更易于维护和适应。由于对象较少依赖于其他对象的内部结构,因此可以更改对象容器而无需重新构造其调用者。

得墨meter耳定律的缺点是,有时它需要编写大量小的“包装”方法,以将方法调用传播到组件。此外,类的接口可能会变得笨重,因为它托管了所包含类的方法,导致类没有内聚的接口。但这也可能是OO设计不良的标志。


我忘记了该法律,谢谢您的提醒。但是我在这里要问的主要优点和缺点,或更准确地说,我应该如何决定使用一种样式而不是另一种样式。
橡树

@Oak,我添加了描述优缺点的引号。
彼得Török

10

我通常尝试使方法链接尽可能地受限制(基于Demeter定律

我唯一的例外是流畅的接口/内部DSL风格编程。

Martin Fowler在“ 领域特定语言”中进行了相同的区分,但是由于违反了命令查询分离的原因,该声明指出:

每个方法应该是执行操作的命令,或者是将数据返回给调用方的查询,但不能两者都应。

福勒在第70页的书中说:

命令查询分离是编程中非常有价值的原则,我强烈建议团队使用它。在内部DSL中使用方法链接的后果之一是,它通常会破坏该原理-每种方法都会更改状态,但会返回一个对象以继续进行链接。我已经使用了许多分贝来贬低那些不遵循命令查询分隔的人,并且会再次这样做。但是流畅的界面遵循不同的规则集,因此我很乐于在此允许它。


3

我认为问题是,您是否使用了合适的抽象。

在第一种情况下,我们有

interface IHasGetA {
    IHasGetB getA();
}

interface IHasGetB {
    IHasGetC getB();
}

interface IHasGetC {
    ITransmogrifyable getC();
}

interface ITransmogrifyable {
    void transmogrify(x,y);
}

其中main是type IHasGetA。问题是:这种抽象是否合适。答案并不简单。在这种情况下,它看起来有些偏离,但这无论如何都是理论上的例子。但是要构造一个不同的示例:

main.getA(v).getB(w).getC(x).transmogrify(y, z);

通常比

main.superTransmogrify(v, w, x, y, z);

因为在后面的示例中都thismain依赖于类型vwxyz。另外,如果每个方法声明都有六个自变量,则代码看起来并不会好得多。

服务定位器实际上需要第一种方法。您不想访问通过服务定位器创建的实例。

因此,“通过”对象可以创建很多依赖关系,如果它基于实际类的属性,则依赖关系就更大。
但是,创建抽象(即提供一个对象)完全是另一回事。

例如,您可能有:

class Main implements IHasGetA, IHasGetA, IHasGetA, ITransmogrifyable {
    IHasGetB getA() { return this; }
    IHasGetC getB() { return this; }
    ITransmogrifyable getC() { return this; }
    void transmogrify(x,y) {
        return x + y;//yeah!
    }
}

main的实例在哪里Main?如果类了解main将依赖关系降低为IHasGetA而不是Main,则您会发现耦合实际上很低。调用代码甚至不知道它实际上是在原始对象上调用最后一个方法,这实际上说明了分离的程度。
您将沿着简洁而又正交的抽象路径前进,而不是深入到实现的内部。


关于参数数量的大量增加的非常有趣的一点。
橡树

2

得墨忒耳定律,如@彼得Török指出,建议的“紧凑型”的形式。

另外,您在代码中明确提及的方法越多,该类所依赖的类就越多,从而增加了维护问题。在您的示例中,紧凑形式取决于两个类,而较长形式取决于四个类。较长的形式不仅违反了得墨meter耳定律;它也使您每次更改四种引用的方法中的任何一种时都可以更改代码(而不是紧凑形式中的两种)。


另一方面,盲目地遵循该法律意味着方法的数量A将会爆炸,A无论如何许多方法可能都希望委托。不过,我仍然同意依赖关系-确实可以大大减少客户端代码所需的依赖关系量。
橡树

1
@Oak:盲目的做任何事情都是不好的。人们需要看一下利弊,并根据证据做出决定。这也包括得墨the耳定律。
CesarGon 2011年

2

我自己为这个问题努力。“深入”不同对象的不利之处在于,当您进行重构时,由于依赖项太多,最终将不得不更改大量代码。此外,您的代码也变得肿,难以阅读。

另一方面,拥有简单地“传递”方法的类也意味着必须在多个地方声明多个方法的开销。

一种减轻这种情况并且在某些情况下适用的解决方案是,通过一个工厂类通过复制来自适当类的数据/对象来构建各种外观对象。这样,您可以针对外观对象进行编码,并且在重构时,只需更改工厂的逻辑即可。


1

我经常发现,使用链接方法更容易理解程序的逻辑。对我来说,customer.getLastInvoice().itemCount()比起我更适合我的大脑customer.countLastInvoiceItems()

是否值得拥有额外的联轴器让您头痛,这取决于您。(我也喜欢小类中的小函数,所以我倾向于链接。我并不是说这是对的,这就是我所做的。)


应该是IMO customer.NrLastInvoices或customer.LastInvoice.NrItems。该链条不太长,因此如果组合的数量较大,则可能不值得展平
Homde 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.