德米特法则与简单类混淆

时间:2016-03-21 15:59:38

标签: c# law-of-demeter

我正在研究计算几何项目。我有表示几何对象的类:Point,LineSegment和class,它们对这些对象执行计算:几何。我对得墨忒耳法和“MethodWithPointAndLineSegment”感到困惑。

class Point
{
    public int X { get; set; }
    public int Y { get; set; }  
    ...
}

class LineSegment
{
    public Point InitialPoint { get; set; }
    public Point TerminalPoint { get; set; }
    ...
}

class Geometry
{
    private ... MethodWithThreePoints(Point p1, Point p2, Point p3)
    {
        // accessing X and Y properties of passed points
        ...
    }

    public ... MethodWithPointAndLineSegment(Point p1, LineSegment segment)
    {
        MethodWithThreePoints(p1, segment.InitialPoint, segment.TerminalPoint);
        ...
    }
}

我的问题是:MethodWithPointAndLineSegment是否违反了德米特法?我想,因为它访问InitialPoint和TerminalPoint属性并将它们作为参数传递给MethodWithThreePoints,后者访问这些点的X和Y属性。换句话说,MethodWithThreePoints使用传递给类方法的对象属性的属性。

如果它违反了得墨忒耳法则,那么我就无法找到解决这个问题的最佳和合理的解决方案。我知道,我可以为LineSegment类添加其他属性以满足LoD:

class LineSegment
{
    ...
    public int InitialPointX 
    { 
        get { return InitialPoint.X; }
        set { InitialPoint.X = value; }
    }
    //etc...
}

但是当我想在MethodWithThreePoints内调用MethodWithPointAndLineSegment时,它会强制我创建新点:new Point(segment.InitialPointX, segment.InitialPointY) ...并将这些新点传递给MethodWithThreePoints。它引入了一些额外的和不需要的性能成本,因为我必须创建新对象并将它们传递给多级访问器返回的构造函数值。

我不确定,对于这个问题和许多类似的问题,什么是最好的解决方案:满足LoD或方便性,在这种情况下性能(这些方法将在短时间内被多次调用以执行算法计算)

欢迎任何建议和解释。

2 个答案:

答案 0 :(得分:1)

我同意说,作为任何良好做法,LoD必须适应您的背景。

我肯定不会告诉你把它送到地狱。它揭示了你设计中的问题。

如果你熟悉OOP,你知道你应该设计一个高度连贯的类,它将数据和行为结合起来,并告诉他们该怎么做(参见Martin Fowler:http://martinfowler.com/bliki/TellDontAsk.html

我这里没有足够的信息来帮助你,但我可以告诉你你的Point和LineSegment类是简单的POCO(没有行为,只有公共get / set)。 它只是没有行为的数据,它实际上不是OOP友好的。 它解释了为什么你很想在服务中操纵这些数据(即使你打电话给你的服务" Geometry")。

我在你的代码中猜测Point和LineSegment代表数据,Geometry代表你想要拥有的行为。

更多的OO设计,让我们说添加翻译行为可能是这样的:

class Point : ITranslate
{
    public Point(int x, int y)
    {
        Y = y;
        X = x;
    }

    public int X { get; private set; }
    public int Y { get; private set; }

    public Point Translate(Translation translation)
    {
        //return a new translated point
    }
}

了解我如何为自己保留数据并公开所需的行为。在这里,我还设计了一个不可变点,但我们也可以这样做:

class Point : ITranslate
{
    public Point(int x, int y)
    {
        Y = y;
        X = x;
    }

    public int X { get; private set; }
    public int Y { get; private set; }

    public void Translate(Translation translation)
    {
        //apply the translation to myself internally on my X and Y
    }
}

当然我需要更多有用的背景,但我希望它已经回答了你的问题。

为了清楚起见,我不是说你必须在这里做到这一点,我只是解释为什么你发现在你的情况下很难尊重Demeter。

答案 1 :(得分:0)

您可以考虑使用F#。通过使用单独的Geometry课程,您已经到了中途。

F#基本上建立在不可变数据的基础之上。因此,不是修改现有的对象,而是修改"修改"实际上返回一个新对象。是的,这会带来性能损失,但通常并不多; HFT算法需要快速但经常使用F#/ Haskell / OCaml,因为减速可以忽略不计,并且结果代码更容易推理。

您在F#中的代码看起来像

type Point = {X: int; Y: int}
type Line = {Start: Point; End: Point}
module Geometry =
  // Some specific examples
  let MiddleOfThreePoints p1 p2 p3 = {X=(p1.X + p2.X + p3.X)/3; Y=(p1.Y + p2.Y + p3.Y)/3}
  let MiddleOfLineAndPoint l p = MiddleOfThreePoints l.Start l.End p
  // You can even use currying to shorten that:
  let MiddleOfLineAndPoint l = MiddleOfThreePoints l.Start l.End

  // Note this returns a *new* point, doesn't modify the existing
  let TranslatePoint x y p = {X = p.X + x; Y = p.Y + y}
  // This returns a *new* list of points; doesn't modify anything
  let TranslatePoints x y points = points |> Seq.map(fun p -> TranslatePoint x y p)
  // A shorter version by using partial application on TranslatePoint
  let TranslatePoints x y points = points |> Seq.map(TranslatePoint x y)
  // An even shorter version by using currying
  let TranslatePoints x y = Seq.map(TranslatePoint x y)

  // "Modify" your object using "with" syntax (creates new object, but convenient syntax)
  let SetY p y = { p with Y = y }

当讨论多线程访问时,不变性具有一些很大的优势(不用担心并发mod,因为没有mod),能够撤消更改(只保留以前版本的列表),并了解什么是'继续(没有正在进行中)。此外,F#语法简洁明了。

F#允许您在需要时作弊并设置可变的内容,并创建常规类/接口,但一般情况下,您在进行算法工作时不应该这样做。