如何解决违反德墨忒尔法的问题?[英] How to solve the violations of the Law of Demeter?

本文是小编为大家收集整理的关于如何解决违反德墨忒尔法的问题?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我和同事为客户设计了一个系统,我们认为我们创建了一个不错的干净设计.但是我介绍了一些耦合问题.我可以尝试创建一个示例设计,其中包括与我们的设计相同的问题,但是如果您原谅我,我将创建我们的设计摘录以支持这个问题.

我们正在开发一个用于注册患者某些治疗方法的系统.为了避免与图像的断开链接,我将把概念UML类图描述为C#样式类定义.

class Discipline {}
class ProtocolKind 
{ 
   Discipline; 
}
class Protocol
{
   ProtocolKind;
   ProtocolMedication; //1..*
}
class ProtocolMedication
{
   Medicine;
}
class Medicine
{
   AdministrationRoute;
}
class AdministrationRoute {}

我将尝试解释有关设计的一些内容,协议是新处理的模板.协议是某种类型的,并且具有需要管理的药物.根据协议,剂量在同一药物方面可能会有所不同(除其他方面),因此该剂量存储在Protocolmedication类中. AdministrationRoute是该药物与协议管理分开管理和创建/更新的方式.

我找到了以下位置,我们将违反Demeter定律:

违反Demeter定律

BLL

内部

例如,在协议中的业务逻辑内部,有依赖于管理的规则.代码将变为

if (!Medicine.AdministrationRoute.Soluble)
{
   //validate constrains on fields
}

存储库

一种将在某个学科中列出所有协议的方法将写为:

public IQueryable<Protocol> ListQueryable(Discipline discipline)
{
    return ListQueryable().Where(p => (p.Kind.Discipline.Id == discipline.Id)); // Entity Frameworks needs you to compare the Id...
}

在用户界面内部

我们认为,我们认为该层的违规行为最严重. GridView的数据指标(必须显示协议的学科的列必须绑定到bink.discipline.name),这是字符串,,因此没有编译时间错误.

.

.
<asp:TemplateField HeaderText="Discipline" SortExpression="Kind.Discipline.Name">
   <ItemTemplate>
      <%# Eval("Kind.Discipline.Name")%>
   </ItemTemplate>
</asp:TemplateField>

所以我认为实际的问题可能是,什么时候可以将其视为Demeter的建议,以及解决对Demeter律法的违规行为的做法?

我有一些想法,但是我会将它们作为答案发布,以便对它们进行评论和投票. (我不确定这是这样做的方法,如果没有,我将删除答案并将其添加到问题中).

推荐答案

我对Demeter定律的后果的理解似乎与Drjokepu不同 - 每当我将其应用于面向对象的代码上时,它都会导致更严格的封装和凝聚力,而不是增加额外的获取器来合同路径在程序代码中.

Wikipedia的规则为

更正式地,Demeter的定律 功能要求m的方法m 一个对象o只能调用 以下种类的方法 对象:

  1. o本身
  2. M的参数
  3. 在M
  4. 中创建/实例化的任何对象
  5. o的直接组件对象

如果您有一种将"厨房"作为参数的方法,Demeter说您不能检查厨房的组件,而不是只能检查即时组件.

写一堆功能只是为了满足塞特的法律

Kitchen.GetCeilingColour()

看起来我完全浪费了时间,而实际上是我完成工作的方式

如果厨房外的一种方法通过了厨房,严格的Demeter也无法调用有关getCeillingColour()结果的任何方法.

但是,无论哪种方式,要点是要删除对结构的依赖性,而不是将结构的表示从一系列链接方法移动到方法的名称.在狗类类中制作诸如Movethelefthindlegforward()之类的方法并不能对实现Demeter做任何事情.相反,呼叫dog.walk(),让狗处理自己的腿.

例如,如果需求发生变化,我也需要天花板高度?

我会重构代码,以便您使用房间和天花板:

interface RoomVisitor {
  void visitFloor (Floor floor) ...
  void visitCeiling (Ceiling ceiling) ...
  void visitWall (Wall wall ...
}

interface Room { accept (RoomVisitor visitor) ; }

Kitchen.accept(RoomVisitor visitor) {
   visitor.visitCeiling(this.ceiling);
   ...
}

或者您可以通过将天花板的参数传递给访问方法,从而完全消除Getters,但这通常会引入脆弱的耦合.

将其应用于医学示例,我希望SolubleAbleadMinstrationRoute能够验证药物,或者至少将药物的validateForsobublAbleadMinistration方法称为验证所需的信息.

但是,Demeter适用于OO系统 - 在数据上运行的对象中数据封装在数据中 - 而不是您要谈论的系统,该系统具有不同的层,该层是在愚蠢的层中传递的数据.我不认为Demeter一定可以轻松地应用于单片或基于消息的系统. (在基于消息的系统中,您无法导航到消息的任何内容,因此您是否喜欢Demeter)

)

其他推荐答案

我知道我将被拒绝彻底歼灭,但我必须说我有点不喜欢塞米特的法律.当然,

之类的东西
dictionary["somekey"].headers[1].references[2]

真的很丑陋,但请考虑一下:

Kitchen.Ceiling.Colour

我什么都不反对.写一堆功能只是为了满足塞米特的法律

Kitchen.GetCeilingColour()

看起来我完全浪费了时间,而实际上是我完成工作的方式.例如,如果要求改变,我也需要天花板高度?有了Demeter的定律,我将不得不在厨房里编写其他功能,以便我可以直接获得天花板的高度,最后,到处我将拥有一堆很小的Getter功能,这是我认为很混乱的事情.<<<<<<<<<<

编辑:让我重新说明我的观点:

这种抽象的东西是否如此重要,以至于我要花时间编写3-4-5个级别的getters/setter?它真的使维护更容易吗?最终用户会获得任何收益吗?值得我时间吗?我不这么认为.

其他推荐答案

传统的违反脱耳人的解决方案是"说明,不要问".换句话说,基于您的状态,您应该告诉托管对象(您持有的任何对象)采取一些措施 - 它将根据自己的状态决定是否做您的要求.

作为一个简单的示例:我的代码使用记录框架,我告诉我的记录器我想输出调试消息.然后,记录器根据其配置(也许未启用调试)来决定是否实际将消息发送到其输出设备.在这种情况下,违反LOD的行为是让我的目的询问记录器是否对消息做任何事情.通过这样做,我现在将代码与对记录器的内部状态的了解(是的,我故意选择了此示例).

但是,此示例的关键点是Logger实现行为.

我认为LOD分解的位置是处理代表 data 的对象, no craveny .

在这种情况下,遍历对象图的IMO与将XPath表达式应用于DOM没有什么不同.并且添加诸如" iShisticationWarranted()"之类的方法是一种更糟糕的方法,因为现在您正在将业务规则分配在对象之间,使它们更难理解.

本文地址:https://www.itbaoku.cn/post/627727.html

问题描述

A colleague and I designed a system for our customer, and in our opinion we created a nice clean design. But I'm having problems with some coupling we've introduced. I could try to create an example design which includes the same problems as our design, but if you forgive me I'll create an extract of our design to support the question.

We're developing a system for the registration of certain treatments for a patients. To avoid having a broken link to image I'll describe the conceptual UML class diagram as a c# style class definition.

class Discipline {}
class ProtocolKind 
{ 
   Discipline; 
}
class Protocol
{
   ProtocolKind;
   ProtocolMedication; //1..*
}
class ProtocolMedication
{
   Medicine;
}
class Medicine
{
   AdministrationRoute;
}
class AdministrationRoute {}

I'll try to explain a bit about the design, a protocol is the template for a new treatment. And a protocol is of a certain Kind and has medications which need to be administered. Per protocol the dosage can differ for the same medicine (amongst other things), so that's stored in the ProtocolMedication class. AdministrationRoute is the way the medicine is administered and iscreated/updated separately from the protocol management.

I've found the following places which we'll have a violation of the Law of Demeter:

Violations of the Law of Demeter

Inside of the BLL

For example, inside the business logic of the ProtocolMedication there are rules which depend on the AdministrationRoute.Soluble property of the medicine. The code would become

if (!Medicine.AdministrationRoute.Soluble)
{
   //validate constrains on fields
}

Inside the repositories

A method which would list all the protocols in a certain Discipline would be written as:

public IQueryable<Protocol> ListQueryable(Discipline discipline)
{
    return ListQueryable().Where(p => (p.Kind.Discipline.Id == discipline.Id)); // Entity Frameworks needs you to compare the Id...
}

Inside the User Interface

We use ASP.NET (no MVC) for the interface of our system, in my opinion this layer currently has the worst violations. The databinding of a gridview (a column which must display the Discipline of a protocol must bind to Kind.Discipline.Name), which are strings, so no compile time errors.

<asp:TemplateField HeaderText="Discipline" SortExpression="Kind.Discipline.Name">
   <ItemTemplate>
      <%# Eval("Kind.Discipline.Name")%>
   </ItemTemplate>
</asp:TemplateField>

So I think the actual question might be, when would it be okay to look at it more as the Suggestion of Demeter, and what can be done to solve the violations of the Law of Demeter?

I've got a few idea's of myself but I'll post them as answers so they can be commented and voted on seperatly. (I'm not sure this is the SO way to do it, if not, I'll delete my answers and add them to the question).

推荐答案

My understanding of consequences of the Law of Demeter seems to be different to DrJokepu's - whenever I've applied it to object oriented code it's resulted in tighter encapsulation and cohesion, rather than the addition of extra getters to contract paths in procedural code.

Wikipedia has the rule as

More formally, the Law of Demeter for functions requires that a method M of an object O may only invoke the methods of the following kinds of objects:

  1. O itself
  2. M's parameters
  3. any objects created/instantiated within M
  4. O's direct component objects

If you have a method which takes 'kitchen' as a parameter, Demeter says you cannot inspect the components of the kitchen, not that you can only inspect the immediate components.

Writing a bunch of functions just to satisfy the Law of Demeter like this

Kitchen.GetCeilingColour()

just looks like a total waste of time for me and actually gets is my way to get things done

If a method outside of Kitchen is passed a kitchen, by strict Demeter it can't call any methods on the result of GetCeilingColour() on it either.

But either way, the point is to remove the dependency on structure rather than moving the representation of the structure from a sequence of chained methods to the name of the method. Making methods such as MoveTheLeftHindLegForward() in a Dog class doesn't do anything towards fulfilling Demeter. Instead, call dog.walk() and let the dog handle its own legs.

For example, what if the requirements change and I will need the ceiling height too?

I'd refactor the code so that you are working with room and ceilings:

interface RoomVisitor {
  void visitFloor (Floor floor) ...
  void visitCeiling (Ceiling ceiling) ...
  void visitWall (Wall wall ...
}

interface Room { accept (RoomVisitor visitor) ; }

Kitchen.accept(RoomVisitor visitor) {
   visitor.visitCeiling(this.ceiling);
   ...
}

Or you can go further and eliminate getters totally by passing the parameters of the ceiling to the visitCeiling method, but that often introduces a brittle coupling.

Applying it to the medical example, I'd expect a SolubleAdminstrationRoute to be able to validate the medicine, or at least call the medicine's validateForSolubleAdministration method if there's information encapsulated in the medicine's class which is required for the validation.

However, Demeter applies to OO systems - where data is encapsulated within the objects which operate upon the data - rather than the system you're talking about, which has different layers an data being passed between the layers in dumb, navigatable structures. I don't see that Demeter can necessarily be applied to such systems as easily as to monolithic or message based ones. (In a message based system, you can't navigate to anything which isn't in the grams of the message, so you're stuck with Demeter whether you like it or not)

其他推荐答案

I know I'm going to get downvoted to total annihilation but I must say I sort of dislike Law of Demeter. Certainly, things like

dictionary["somekey"].headers[1].references[2]

are really ugly, but consider this:

Kitchen.Ceiling.Colour

I have nothing against this. Writing a bunch of functions just to satisfy the Law of Demeter like this

Kitchen.GetCeilingColour()

just looks like a total waste of time for me and actually gets is my way to get things done. For example, what if the requirements change and I will need the ceiling height too? With the Law of Demeter, I will have to write an other function in Kitchen so I can get the Ceiling height directly, and in the end I will have a bunch of tiny getter functions everywhere which is something I would consider quite a mess.

EDIT: Let me rephrase my point:

Is this level of abstracting things so important that I shall spend time on writing 3-4-5 levels of getters/setters? Does it really make maintenance easier? Does the end user gain anything? Is it worth my time? I don't think so.

其他推荐答案

The traditional solution to Demeter violations is "tell, don't ask." In other words, based on your state, you should tell a managed object (any object you hold) to take some action -- and it will decide whether to do what you ask, depending on its own state.

As a simple example: my code uses a logging framework, and I tell my logger that I want to output a debug message. The logger then decides, based on its configuration (perhaps debugging isn't enabled for it) whether or not to actually send the message to its output devices. A LoD violation in this case would be for my object to ask the logger whether it's going to do anything with the message. By doing so, I've now coupled my code to knowledge of the logger's internal state (and yes, I picked this example intentionally).

However, the key point of this example is that the logger implements behavior.

Where I think the LoD breaks down is when dealing with an object that represents data, with no behavior.

In which case, IMO traversing the object graph is no different than applying an XPath expression to a DOM. And adding methods such as "isThisMedicationWarranted()" is a worse approach, because now you're distributing business rules amongst your objects, making them harder to understand.