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