Закон Деметры путаница с простыми классами

Я работаю над проектом вычислительной геометрии. У меня есть классы, представляющие геометрические объекты: Point, LineSegment и class, который выполняет вычисления для этих объектов: Geometry. Я запутался с Законом Деметры и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 ответа

Решение

Я бы согласился сказать, что LoD, как и любая хорошая практика, является руководством, которое вы должны адаптировать к своему контексту.

Я, конечно, не скажу вам отправить его в ад, хотя. Это выявляет проблемы в вашем дизайне.

Если вы знакомы с ООП, вы знаете, что вам следует разработать очень связный класс, сочетающий в себе данные и поведение, и сказать им, что делать (см. Мартин Фаулер: http://martinfowler.com/bliki/TellDontAsk.html).

У меня недостаточно информации, чтобы помочь вам, но я могу сказать, что ваши классы Point и LineSegment являются простыми POCO (без поведения, только общедоступные get/set). Это только данные без поведения, что не очень удобно для ООП. Это объясняет, почему у вас возникает соблазн манипулировать этими данными в сервисе (даже если вы называете свой сервис "Геометрия").

Думаю, в вашем коде Point и LineSegment представляют данные, а Geometry - поведение, которое вы хотели бы иметь.

Более ОО дизайн, скажем, добавить поведение перевода может быть что-то вроде:

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
    }
}

Конечно, мне нужно больше контекста, чтобы быть более полезным, но я надеюсь, что он уже отвечает на ваш вопрос.

Просто чтобы прояснить ситуацию, я не говорю, что вам здесь обязательно нужно это делать, я просто объясняю, почему вам трудно уважать Деметру в вашем случае.

Вы можете посмотреть на использование 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 }

Неизменность имеет некоторые большие преимущества, когда речь идет о многопоточном доступе (не беспокойтесь о параллельных модах, потому что модов нет), возможности отменить изменения (просто сохраните список предыдущих версий) и понимание того, что происходит (ничего не происходит), Плюс синтаксис F# хорош и лаконичен.

F# позволяет вам обманывать и задавать изменчивость, когда вам это необходимо, и создавать обычные классы / интерфейсы, но в целом вам не следует особенно это делать при выполнении алгоритмической работы.

Другие вопросы по тегам