受此问题的启发,但希望不会重复。

我了解在服务与其他服务交互的情况下,得墨meter耳定律非常有用,例如,在单元中模拟其他服务要容易得多测试。
服务与数据模型的交互又如何呢?

我有一个不变类的层次结构:

public final class Document {
    private final List<Page> pages;
    // Constructor, getter, ...
}

public final class Page {
    private final List<Paragraph> paragraphs;
    // Constructor, getter, ...
}

public final class Paragraph {
    private final List<Line> lines;
    // Constructor, getter, ...
}


假设我要对文档中的某些行进行某些操作doc

public void doSomething(Document doc) {
    for (Page page : doc.getPages()) {
        for (Paragraph para : page.getParagraphs()) {
            for (Line line : para.getLines()) {
                if (someCondition(line)) {
                    someAction(line);
                }
            }
        }
    }


现在,据我所知,上述代码不符合法律规定
但是,我不想不惜一切代价强制执行法律,并使用数十种方法将模型(这里是Document类)弄乱了:

public List<Line> filterWrtSomeCondition();
public List<Line> filterWrtSomeOtherCondition();


或者也许还有第三种方法?我怀疑Demeter法则主要适用于与其他服务交互的服务。这是有效的解释吗?

#1 楼

有关LoD的某些观点,请阅读“ Demeter法则不是点算练习”。特别是标题“我与法律抗争,而法律获胜”下的部分。

在这种情况下,我认为页面,段落和行是文档中非常合乎逻辑的子组件。这些都有不同但相关的行为。每种方法也有不同的用途。

正如您所说,强制文档处理与子组件的交互将导致混乱(并且有更多机会引入麻烦的问题)。

LoD的重要之处在于它旨在减少耦合。但是,向Document添加方法并不能真正减少耦合,因为您仍在编写“ do x to y y”的代码-您只是在要求Document为您完成此操作。

Don'不要误会我的意思,LoD非常有用。但是像所有原理一样,必须正确使用它才能保持有用。

(有趣的是,类似问题的答案与SO提出的类似。)

结论

简而言之,与文档对话而不是包含页面,段落和行对我来说没有任何好处。坦白说,我认为您没有任何投资回报。

评论


\ $ \ begingroup \ $
有趣的参考,特别是:“如果LoD是关于封装(也称为信息隐藏)的,那么为什么要隐藏对象的结构,而该结构正是人们感兴趣的并且不太可能改变?”。我倾向于认为,在处理数据结构时,应该将面向对象的设计原则放在负责任和行为的对象上,而数据结构实际上是另一种对象。
\ $ \ endgroup \ $
– EricBréchemier
11年1月22日在8:52



#2 楼

我会争论对点...

LoD会说此客户代码与文档的特定模型绑定在一起。最后,代码对文档的各行确实很感兴趣。将代码绑定到getPage / getParagraph / getLine模型有多合理?

完全有理由相信页面将总是由段落和由行组成的段落组成,但是我不认为这是合理的。是唯一的考虑因素。要问的另一个问题是Document还应支持哪些其他模型?换句话说:

“将文档视为一系列行而不用考虑它们所在的页面或段落是合理的吗?”

所以我会给Document一个getLines()方法,而不是强制我们的代码通过页面/段落/线模型。

这个getLines()方法也是一件好事,因为:如果它对我们的Document合理的客户以这种方式看东西,Document也可能以这种方式看东西是合理的。就是说,Document是否有可能具有一种高效的结构,可以在不涉及页面和段落的情况下(如果不是现在,则可能是将来)在行与行之间移动?如果是这样,强制客户端代码通过getPage / getParagraph / getLine会损害使用该高效结构的机会。

在每种可能的情况下,我还认为其不合理的“强制文档处理与子组件的交互”,因此必须有一个快乐的媒介。作为反例,请考虑:

“将图书馆看成一系列的行而不考虑它们所在的文档是否合理?”

我不知道认为我不会为我的getLines()类提供Library方法。

#3 楼

我也同意该代码违反了LoD。调用者将实现绑定到嵌套对象的模型上。

添加一堆过滤器方法似乎也不是正确的主意,但这是变相的机会。您确实想要一种可以采用“动词”的通用过滤方法来应用于匹配元素。或者,可以选择一个带谓词的过滤器方法,只返回匹配元素的集合。

这是LoD的有用功能之一。它可以帮助您发现您在较低抽象级别上进行操作的位置,并提高该级别。在这种情况下,一旦引入一般谓词或动词,您很可能会找到其他使用相同习语的地方。从局部清理开始,可以发现非常普遍的表达方式。

#4 楼

我会说这并不会使模型变得混乱。您实际上是通过不公开文档类的内部实现来保护自己的。整个想法是将您的所有行为放入类中,而不是让外部代码以不传递其内部不变量的方式修改类。

也许这样做也很有意义在您的Document类中创建专门的子类,并且仅具有与您的Document用法相关的方法。您不必拥有一站式商店,就可以绝对完成所有事情,因为在所有情况下,这可能都不是您的主要用例。

评论


\ $ \ begingroup \ $
感谢您的回答!尽管我没有强调,但我已经提到该模型是不可变的,因此一旦创建该模型就无法“破坏”该模型。这会改变什么吗?
\ $ \ endgroup \ $
–博洛
2011年1月21日在17:41

\ $ \ begingroup \ $
不过,我对添加子类表示怀疑。首先,我可以清楚地命名和记录Document类,但是如何使用评估文本可读性的方法或查找给定短语出现的方法来命名Document子类?如:“如果您不能命名,则它不应该是一个类。”
\ $ \ endgroup \ $
–博洛
2011年1月21日在17:42

\ $ \ begingroup \ $
没有赶上不变的部分。至少这样做可以使您无法破坏它,但是公开内部实现仍然有效。在不知道您的用例的情况下,我只是在猜测并说子类可能是解决您添加许多方法(它们可能会分解)的一种有效方法。
\ $ \ endgroup \ $
– Mark Loeser
2011年1月21日在17:59