Закон Деметры и Классовые Конструкторы - PullRequest
5 голосов
/ 20 июля 2009

Закон Деметры не препятствует передаче объектов в конструкторы классов. Однако он запрещает возвращать тот же объект позже и вызывать метод для получения скалярного значения. Вместо этого предполагается создать прокси-метод, который вместо этого возвращает скалярное значение. Мой вопрос: почему допустимо передавать объект в конструктор класса, но недопустимо возвращать тот же объект позже и извлекать из него значение?

Ответы [ 3 ]

13 голосов
/ 20 июля 2009

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

Вы передаете объект в конструктор, чтобы сообщить своему новому объекту, как себя вести, но не ваше дело, будет ли объект сохранять объект этого параметра вокруг, или сохраняет его копию, или просто смотрит на него один раз и забывает это когда-либо существовало. Имея метод getMyParameterBack, вы предоставили всем будущим реализациям возможность создавать весь этот объект по требованию, а также чтобы все клиенты связывались с двумя интерфейсами вместо одного.

Например, если вы передаете параметр URL в конструктор объекта HTTPRequest, это не означает, что HTTPRequest должен иметь метод getURL, который возвращает объект URL, для которого вызывающая сторона должна затем вызвать getProtocol, getQueryString и т. Д. Если кто-то, имеющий объект HTTPRequest, может захотеть узнать протокол запроса, он должен (согласно закону) выяснить это, вызвав getProtocol для объекта, который у него есть, а не для какого-либо другого объекта, который, как ему известно, хранится внутри HTTPRequest. .

Идея состоит в том, чтобы уменьшить связь - без закона Деметры пользователь должен знать интерфейс для HTTPRequest и URL, чтобы получить протокол. С Законом им нужен только интерфейс к HTTPRequest. И HTTPRequest.getProtocol () явно может возвращать «http» без необходимости какого-либо объекта URL для участия в обсуждении.

Тот факт, что иногда пользователь объекта запроса оказывается тем, кто его создал, и, следовательно, также использует интерфейс URL для передачи параметра, ни здесь, ни там. Не все пользователи объектов HTTPRequest создали их сами. Таким образом, клиенты, которые в соответствии с законом имеют право на доступ к URL-адресу, поскольку они создали его самостоятельно, могут сделать это таким образом, вместо того, чтобы отозвать его обратно из запроса. Клиенты, которые не создали URL, не могут.

Лично я думаю, что Закон Деметры, как обычно говорится в простой форме, нарушен. Серьезно ли они говорят, что если у моего объекта есть строковое поле Имя, и я хочу знать, содержит ли Имя какие-либо символы, не относящиеся к ASCII, то я должен либо определить метод NameContainsNonASCIICharacters в моем объекте, а не смотреть на саму строку, либо добавить функцию visitName в класс, принимающий функцию обратного вызова, чтобы обойти ограничение, гарантируя, что строка является параметром для функции, которую я написал? Это совсем не меняет связь, оно просто заменяет методы получения на методы посетителя. Должен ли каждый класс, который возвращает целое число, иметь полный набор арифметических операций, если я хочу манипулировать возвращаемым значением? getPriceMultipliedBy (int n)? Конечно, нет.

Для чего это полезно, так это то, что когда вы ломаете его, вы можете спросить себя, почему вы ломаете его, и можете ли вы разработать лучший интерфейс, не нарушая его. Часто вы можете, но на самом деле это зависит от того, о каких объектах вы говорите. Некоторые интерфейсы могут быть безопасно соединены с огромным количеством кода - такими как целые числа, строки и даже URL, которые представляют широко используемые концепции.

4 голосов
/ 20 июля 2009

Ответ JP довольно хороший, так что это всего лишь дополнение, а не разногласие или другая замена.

Насколько я понимаю, эта эвристика заключается в том, что вызов A не должен прерываться из-за изменения класса B. Поэтому, если вы объединяете свои вызовы с помощью a.b.foo (), то интерфейс A становится зависимым от B, нарушая правило. Вместо этого вы должны вызывать a.BFoo (), который для вас вызывает b.foo ().

Это хорошее эмпирическое правило, но оно может привести к неуклюжему коду, который на самом деле не столько решает зависимость, сколько закрепляет ее. Теперь А должен предлагать BFoo навсегда, даже когда B больше не предлагает Foo. Ничего особенного, и, возможно, было бы лучше, по крайней мере, в некоторых случаях, если бы изменения в B сломали вызывающего, который хочет Foo, а не самого B.

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

4 голосов
/ 20 июля 2009

Идея в том, что вы говорите только со своими непосредственными друзьями. Итак, вы этого не делаете ...

var a = new A();
var x = a.B.doSomething();

Вместо этого вы делаете это ...

var a = new A();
var x = a.doSomething(); // where a.doSomething might call b.doSomething();

У него есть свои преимущества, так как для вызывающих абонентов все становится проще (Car.Start () по сравнению с Car.Engine.Start ()), но вы получаете множество небольших методов переноса. Вы также можете использовать шаблон Mediator , чтобы смягчить этот тип "нарушения".

...