Согласно закону Деметры, разрешено ли классу возвращать одного из его членов?

13

У меня три вопроса относительно закона Деметры.

Помимо классов, которые были специально назначены для возврата объектов - таких как классы фабрики и компоновщика - нормально ли, чтобы метод возвращал объект, например, объект, удерживаемый одним из свойств класса, или будет нарушать закон Деметры (1) ? И если он нарушает закон Деметры, имеет ли значение, если возвращаемый объект является неизменным объектом, представляющим фрагмент данных и содержащий только геттеры для этих данных (2а)? Или такой ValueObject сам по себе является антишаблоном, потому что все, что делается с данными в этом классе, выполняется вне класса (2b)?

В псевдокоде:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Я подозреваю, что закон Деметера запрещает такую ​​модель, как приведенная выше. Что я могу сделать, чтобы это doSomethingElse()можно было назвать, не нарушая закон (3)?

user2180613
источник
9
Многие люди (в частности, функциональные программисты) рассматривают Закон Деметры как анти-паттерн. Другие рассматривают это как «Иногда полезное предложение Деметры».
Дэвид Хаммен
1
Я собираюсь поднять этот вопрос, потому что он иллюстрирует абсурдность Закона Деметры. Да, LoD «иногда полезен», но обычно он глупый.
user949300
Как xнастроиться?
candied_orange
@CandiedOrange x- это просто другое свойство, значением которого является просто некоторый объект, который может вызываться doSomethingElse.
user2180613
1
Ваш пример не нарушает LOD. LOD - это предотвращение связи клиентов объекта с внутренними деталями объекта или соавторами. Вы хотите избежать таких вещей, как user.currentSession.isLoggedIn(). потому что он связывает клиентов userне только со userсвоим сотрудником, но и с ним session. Вместо этого вы хотите иметь возможность писать user.isLoggedIn(). обычно это достигается добавлением isLoggedInметода user, делегату реализации которого currentSession.
Иона

Ответы:

7

Во многих языках программирования все возвращаемые значения являются объектами. Как уже говорили другие, неспособность использовать методы возвращаемых объектов заставляет вас вообще ничего не возвращать. Вы должны спросить себя: «Каковы обязанности класса А, В и С?» Вот почему использование метасинтаксических имен переменных, таких как A, B и C, всегда запрашивает ответ «это зависит», потому что в этих терминах нет обязанностей наследования.

Возможно, вы захотите взглянуть на Domain Driven Design, который даст вам более детальный набор эвристик, чтобы рассуждать о том, куда должна идти функциональность и кто должен что вызывать.

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

Упрощенный LOD намного лучше выражен в DDD как правила доступа к агрегатам . Никакие внешние объекты не должны содержать ссылки на элементы агрегатов. Должна быть только корневая ссылка.

Помимо DDD, вы, по крайней мере, хотите разработать собственные наборы стереотипов для своих классов, подходящие для вашей области. Применяйте правила в отношении этих стереотипов при разработке вашей системы.

Также всегда помните, что все эти правила должны управлять сложностью, а не ограничивать себя.

Джереми
источник
1
Предоставляет ли DDD какие-либо правила относительно того, когда члены класса, такие как данные, могут быть представлены (то есть должны существовать методы получения)? Все дискуссии вокруг Law of Demeter, tell, don't ask, getters are evil, object encapsulationи , single responsibility principleкажется, сводятся к этому вопросу: когда следует делегации на объект домена используется в отличие от получения значения объекта и делать что - то с этим? Было бы очень полезно иметь возможность применять нюансы квалификации в ситуациях, когда такое решение должно быть принято.
user2180613
Существуют руководящие принципы, такие как «получатели являются злом», потому что многие люди рассматривают экземпляры классов как структуры данных (пакет данных для передачи). Это не объектно-ориентированное программирование. Объекты - это наборы функций / поведения, которые фактически обеспечивают некоторую работу. Объем работ определяется ответственностью. Эта работа, очевидно, будет создавать объекты, которые возвращаются из методов и т. Д. Если ваш класс на самом деле выполняет значимую работу, которую вы можете четко определить за пределами «представления объекта данных X с атрибутами Y и Z», то вы на правильном пути. , Я бы не стал это подчеркивать.
Джереми
Если говорить более подробно, когда вы используете много геттеров / аскеров, это обычно означает, что у вас где-то есть какой-то большой метод, который организует все, что Фаулер называет скриптом транзакции. Прежде чем начать использовать геттер, спросите себя ... почему я запрашиваю эти данные из объекта? Почему эта функциональность отсутствует в методе объекта? Если этот класс не несет ответственности за реализацию этой функциональности, тогда используйте метод получения. Книга Фаулера « Рефакторинг» ( martinfowler.com/books/refactoring.html ) - книга по эвристике для таких вопросов.
Джереми
В своей книге «Внедрение доменного дизайна» Вон Вернон упоминает Закон Деметры и Скажи, не спрашивай в гл. 10, с. 382. Возможно, стоит взглянуть, если у тебя есть к нему доступ.
Джереми
6

Согласно закону Деметры, разрешено ли классу возвращать одного из его членов?

Да, это, безусловно, так и есть.

Давайте посмотрим на основные моменты :

  • Каждый юнит должен иметь только ограниченные знания о других юнитах: только юниты, «тесно» относящиеся к текущему юниту.
  • Каждый юнит должен разговаривать только со своими друзьями; не разговаривай с незнакомцами
  • Поговорите только со своими непосредственными друзьями.

Все три из них оставляют у вас один вопрос: кто друг?

Принимая решение о том, что возвращать, Закон Деметры или принцип наименьшего знания (LoD) не требует от вас защиты от кодировщиков, которые настаивают на его нарушении. Это диктует, что вы не заставляете кодеров нарушать его.

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

Означает ли это, что вы свободны вникать в кодовую базу, объединяющую все, что вы хотите? Нет. Соединяйте только то, что должно было быть вместе. В противном случае цепь может измениться, и вдруг ваши вещи сломаются. Это то, что подразумевается под друзьями.

Длинные цепи могут быть предназначены для. Свободные интерфейсы, iDSL, большая часть Java8 и старый добрый StringBuilder предназначены для создания длинных цепочек. Они не нарушают LoD, потому что все в цепочке предназначено для совместной работы и обещают продолжать работать вместе. Вы нарушаете деметру, когда цепляете вместе вещи, которые никогда не слышали друг о друге. Друзья - это те, кто обещал, что ваша сеть будет работать. Друзья друзей не сделали.

Помимо классов, которые были специально назначены для возврата объектов - таких как классы фабрики и компоновщика - нормально ли, чтобы метод возвращал объект, например, объект, удерживаемый одним из свойств класса, или будет нарушать закон Деметры (1) ?

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

И если он нарушает закон Деметры, имеет ли значение, если возвращаемый объект является неизменным объектом, который представляет собой часть данных и содержит только геттеры для этих данных (2)?

Неизменное это хорошо, но не имеет значения здесь. Пройдя сквозь длинную цепочку, не становится лучше. То, что делает это лучше, отделяет получение от использования. Если вы используете, спросите, что вам нужно в качестве параметра. Не иди на охоту, углубляясь в добытчиков незнакомцев.

В псевдокоде:

Я подозреваю, что закон Деметера запрещает такую ​​модель, как приведенная выше. Что я могу сделать, чтобы функция doSomethingElse () могла быть вызвана без нарушения закона (3)?

Прежде чем говорить о том, x.doSomethingElse(a)понимаю, что вы в основном написали

b.getA().doSomething()

Теперь LoD - это не упражнение по подсчету точек . Но когда вы создаете цепочку, вы говорите, что знаете, как получить A(используя B), и знаете, как использовать A. Ну Aи Bлучше быть близкими друзьями , потому что вы только в сочетании их вместе.

Если вы только что попросил что - нибудь рукой для Вас , Aкак вещь , которую вы могли бы использовать Aи не волновало , откуда она и Bмогла прожить жизнь счастливой и свободной от вашей одержимости получать Aот B.

Что касается x.doSomethingElse(a)отсутствия подробностей о том, откуда xвзялось, LoD вообще нечего сказать по этому поводу.

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

Так что да, классу разрешено возвращать одного из его членов в LoD. Когда вы это сделаете, вы должны уточнить, дружит ли этот член с вашим классом, потому что, если это не так, какой-то клиент может попытаться соединить вас с этим классом, используя вас, чтобы получить его, прежде чем использовать его. Это важно, потому что теперь то, что вы возвращаете, должно всегда поддерживать это использование.

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

candied_orange
источник
Свободные интерфейсы не являются исключением из LOD из-за некоторого понятия дружелюбия .... В свободном интерфейсе каждый из методов в цепочке вызывается для одного и того же объекта, потому что все они возвращают себя. settings.darkTheme().monospaceFont()просто синтаксический сахар дляsettings.darkTheme(); settings.monospaceFont();
Иона
3
@Jonah Возвращение себя - это абсолютное дружелюбие. И не все беглые интерфейсы делают. Многие iDSL также свободно и возвращают различные типы, чтобы изменить доступные методы в разных точках. Рассмотрим jOOQ , пошагового строителя , волшебника и моего собственного кошмарного строителя монстров . Свободный стиль. Не шаблон.
candied_orange
2

Помимо классов, которые были специально назначены для возвращения объектов [...]

Я не совсем понимаю этот аргумент. Любой объект может вернуть объект в результате вызова сообщения. Особенно, если вы думаете «все как объект».

Классы, однако, являются единственными объектами, которые могут создавать новые объекты своего вида, отправляя им «новое» сообщение (или часто заменяемое новым ключевым словом в большинстве распространенных языков ООП)


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

И вы не хотели бы, чтобы объект C знал о деталях реализации B. Это нежелательная зависимость от объекта A с точки зрения объекта C.

Поэтому вы можете спросить себя, не знает ли C, зная A, слишком много для той работы, которую он должен выполнять, независимо от LOD.

Есть случаи, когда это может быть законным, например, уместно запрашивать объект коллекции для одного из его элементов и не нарушать никакого правила Деметры. С другой стороны, запрашивать объект Book для объекта SQLConnection рискованно и его следует избегать.

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

NikoGJ
источник
0

Помимо классов, которые были специально назначены для возврата объектов - таких как классы фабрики и конструктора - нормально ли для метода возвращать объект?

Почему бы не быть? Похоже, вы предполагаете, что необходимо сообщить своим коллегам-программистам, что класс специально предназначен для возврата объектов или он вообще не должен возвращать объекты. На языках, где все является объектом, это сильно ограничит ваши возможности, так как вы вообще не сможете ничего вернуть.

Роберт Харви
источник
Пример о неявном b.getA().doSomething()иx.doSomethingElse(b.getA())
user2180613
A a = b.getA(); a.DoSomething();- вернуться к одной точке.
Роберт Харви
2
@ user2180613 - Если вы хотите спросить об этом, b.getA().doSomething()и x.doSomethingElse(b.getA())вы должны были явно спросить об этом. Похоже, ваш вопрос, похоже, о this.b.getA().
Дэвид Хаммен
1
Как Aне член C, а не создано в Cи не предоставляется C, кажется , что использование Aв C(в любой форме) нарушает Lod. Подсчет точек - это не то, чем занимается LoD, это всего лишь иллюстративный инструмент. Можно преобразовать Driver().getCar().getSteeringWheel().getFrontWheels().toRight()в отдельные операторы, каждый из которых содержит одну точку, но нарушение LoD все еще существует. Я читал во многих сообщениях на этом форуме , что выполнение действия должны быть переданы на объект , который реализует фактическое поведение т tell don't ask. Возвращаемые значения, кажется, конфликтуют с этим.
user2180613
1
Я предполагаю, что это правда, но это не отвечает на вопрос.
user2180613
0

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

С другой стороны, если интерфейс вашего класса заявляет, что он будет предоставлять экземпляр хорошо известного класса A, это нормально. Если вам повезло, и вы можете сделать это, предоставив ученика своего класса, хорошо для вас. Если вы изменили этот элемент с A на B, вам нужно будет переписать метод, который возвращает A, очевидно, теперь он должен будет сконструировать его и заполнить доступными данными, а не однострочными, возвращающими член. Интерфейс вашего класса будет неизменным.

gnasher729
источник