Dav*_*man 36 language-agnostic design-patterns coupling law-of-demeter
我和一位同事为我们的客户设计了一个系统,我们认为我们创造了一个漂亮干净的设计.但是我遇到了一些我们引入的耦合问题.我可以尝试创建一个包含与我们的设计相同的问题的示例设计,但如果您原谅我,我将创建一个设计摘录来支持这个问题.
我们正在开发一种为患者注册某些治疗方案的系统.为了避免链接到图像,我将概念性UML类图描述为ac#样式类定义.
class Discipline {}
class ProtocolKind
{
Discipline;
}
class Protocol
{
ProtocolKind;
ProtocolMedication; //1..*
}
class ProtocolMedication
{
Medicine;
}
class Medicine
{
AdministrationRoute;
}
class AdministrationRoute {}
Run Code Online (Sandbox Code Playgroud)
我将尝试解释一下设计,协议是新治疗的模板.并且协议属于某种类型并且具有需要施用的药物.根据协议,对于相同的药物(以及其他事物),剂量可以不同,因此存储在ProtocolMedication类中.AdministrationRoute是药物的管理方式,与协议管理分开创建/更新.
我发现以下地方违反了得墨忒耳法则:
例如,在ProtocolMedication的业务逻辑中,存在依赖于药物的AdministrationRoute.Soluble属性的规则.代码将成为
if (!Medicine.AdministrationRoute.Soluble)
{
//validate constrains on fields
}
Run Code Online (Sandbox Code Playgroud)
列出某个学科中所有协议的方法将写成:
public IQueryable<Protocol> ListQueryable(Discipline discipline)
{
return ListQueryable().Where(p => (p.Kind.Discipline.Id == discipline.Id)); // Entity Frameworks needs you to compare the Id...
}
Run Code Online (Sandbox Code Playgroud)
我们使用ASP.NET(没有MVC)作为我们系统的接口,在我看来这个层目前有最严重的违规行为.gridview的数据绑定(必须显示协议的Discipline的列必须绑定到Kind.Discipline.Name),这是字符串,因此没有编译时错误.
<asp:TemplateField HeaderText="Discipline" SortExpression="Kind.Discipline.Name">
<ItemTemplate>
<%# Eval("Kind.Discipline.Name")%>
</ItemTemplate>
</asp:TemplateField>
Run Code Online (Sandbox Code Playgroud)
所以我认为实际的问题可能是,什么时候可以将其视为Demeter的建议,以及可以采取哪些措施来解决违反Demeter法的问题?
我对自己有一些想法,但我会将它们作为答案发布,以便他们可以单独评论和投票.(我不确定这是不是这样做的方法,如果没有,我会删除我的答案并将其添加到问题中).
Pet*_*ham 30
我对Demeter法的后果的理解似乎与DrJokepu的不同 - 每当我将它应用于面向对象的代码时,它会导致更紧密的封装和内聚,而不是在程序代码中向合同路径添加额外的getter.
维基百科的规则为
更正式地说,函数的Demeter法则要求对象O的方法M只能调用以下类型的对象的方法:
- O本身
- M的参数
- 在M中创建/实例化的任何对象
- O的直接组件对象
如果你有一个以'厨房'为参数的方法,Demeter说你不能检查厨房的组件,而不是你只能检查直接的组件.
写一堆函数只是为了满足这样的Demeter法则
Run Code Online (Sandbox Code Playgroud)Kitchen.GetCeilingColour()看起来对我来说总是浪费时间,实际上是我完成工作的方式
如果Kitchen之外的方法通过厨房,严格的Demeter它也不能调用GetCeilingColour()的结果上的任何方法.
但无论哪种方式,重点是消除对结构的依赖,而不是将结构的表示从一系列链式方法移动到方法的名称.在Dog类中创建诸如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);
...
}
Run Code Online (Sandbox Code Playgroud)
或者你可以通过将天花板的参数传递给visitCeiling方法来进一步消除吸气剂,但这通常会引入脆性耦合.
将它应用于医学示例,我希望SolubleAdminstrationRoute能够验证药物,或者至少调用药物的validateForSolubleAdministration方法,如果在药物类中封装了验证所需的信息.
但是,Demeter适用于OO系统 - 其中数据封装在对数据进行操作的对象中 - 而不是您正在讨论的系统,其具有不同的层,数据在哑层,可导航结构的层之间传递.我不认为Demeter必须像单片或基于消息的那样容易地应用于这样的系统.(在基于消息的系统中,您无法导航到不在消息克中的任何内容,因此无论您是否喜欢,您都会被Demeter困住)
Tam*_*ege 21
我知道我会被彻底毁灭,但我必须说我不喜欢得墨忒耳法.当然,像
dictionary["somekey"].headers[1].references[2]
Run Code Online (Sandbox Code Playgroud)
真的很难看,但考虑一下:
Kitchen.Ceiling.Coulour
Run Code Online (Sandbox Code Playgroud)
我没有反对这一点.写一堆函数只是为了满足这样的Demeter法则
Kitchen.GetCeilingColour()
Run Code Online (Sandbox Code Playgroud)
看起来对我来说总是浪费时间,实际上是我完成工作的方式.例如,如果要求发生变化并且我也需要天花板高度怎么办?根据Demeter法则,我将不得不在Kitchen中编写另一个函数,这样我就可以直接获得Ceiling高度,最后我会在各处获得一些微小的getter函数,这些都是我认为非常混乱的东西.
编辑:让我重新说一下我的观点:
抽象事物的这种程度是否如此重要以至于我会花时间写3-4-5级的吸气剂/制定者?它真的能让维护更轻松吗?最终用户获得了什么吗?值得我花时间吗?我不这么认为.
kdg*_*ory 11
Demeter违规的传统解决方案是"告诉,不要问".换句话说,根据您的状态,您应该告诉托管对象(您拥有的任何对象)采取某些操作 - 它将根据自己的状态决定是否按照您的要求执行操作.
举个简单的例子:我的代码使用了一个日志框架,我告诉我的记录器我想输出一个调试消息.然后,记录器根据其配置(可能未启用调试)决定是否实际将消息发送到其输出设备.在这种情况下,LoD违规将是我的对象询问记录器它是否会对消息做任何事情.通过这样做,我现在将我的代码与记录器内部状态的知识相结合(是的,我故意选择了这个例子).
但是,此示例的关键点是记录器实现了行为.
当我认为LOD的分解是表示对象打交道时的数据,与无行为.
在这种情况下,IMO遍历对象图与将XPath表达式应用于DOM没有什么不同.添加诸如"isThisMedicationWarranted()"之类的方法是一种更糟糕的方法,因为现在您在对象中分配业务规则,使其更难理解.
| 归档时间: |
|
| 查看次数: |
21522 次 |
| 最近记录: |