Відповідно до закону Деметра, чи дозволяється класу повернути одного зі своїх членів?


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)?


9
Дуже багато людей (зокрема функціональні програмісти) розглядають Закон Деметера як анти-закономірність. Інші розглядають це як "Іноді корисну пропозицію Деметра".
Девід Хаммен

1
Я збираюся підтримати це питання, тому що воно ілюструє безглузді Закон Деметера. Так, LoD є "періодично корисним", але його зазвичай дурно.
user949300

Як xналаштовується?
candied_orange

@CandiedOrange x- це лише інша властивість, значення якої - лише якийсь об'єкт, який можна викликати doSomethingElse.
користувач2180613

1
Ваш приклад не порушує LOD. LOD полягає у тому, щоб клієнти об'єкта не могли з'єднати внутрішні реквізити або співпрацівники з цим об'єктом. ви хочете уникати таких речей user.currentSession.isLoggedIn(). тому що він з'єднує клієнтів userне тільки, userале і його співпрацівника session. Натомість ви хочете вміти писати user.isLoggedIn(). зазвичай це досягається додаванням isLoggedInметоду, до userвиконання якого делегуються currentSession.
Йона

Відповіді:


7

У багатьох мовах програмування всі повернені значення є об'єктами. Як говорили інші, неможливість використовувати методи повернених об'єктів змушує вас взагалі нічого не повертати. Ви повинні запитати себе: "Які обов'язки класів A, B і C?" Ось чому, використовуючи метасинтаксичні імена змінних, такі як A, B і C, завжди вимагають відповіді "це залежить", оскільки немає спадкових обов'язків у цих термінах.

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

Ваше друге запитання, що стосується незмінних об'єктів, відповідає поняттю
ціннісного об'єкта порівняно з об'єктом Entity. в DDD Ви можете передавати об'єкти цінності навколо майже без обмежень. Це не відповідає дійсності для об'єктів сутності.

Спрощений LOD значно краще виражається в DDD як правила доступу до агрегатів . Жоден зовнішній об'єкт не повинен містити посилань на членів сукупностей. Потрібно мати лише кореневе посилання.

Окрім DDD, ви хоч хочете розробити власні набори стереотипів для своїх класів, які відповідають вашому домену. Дотримуйтесь правил щодо цих стереотипів під час проектування системи.

Також завжди пам’ятайте, що всі ці правила - керувати складністю, а не заважати собі.


1
Чи надає DDD які-небудь правила щодо того, коли члени класу, такі як дані, можуть бути викриті (тобто, щоб отримати існування)? Всі дискусії навколо Law of Demeter, tell, don't ask, getters are evil, object encapsulationі , single responsibility principleздається, зводяться до цього питання: коли слід делегації на об'єкт домена використовується на відміну від отримання значення об'єкта і робити що - то з цим? Було б дуже корисно мати можливість застосовувати нюансову кваліфікацію у ситуаціях, коли таке рішення має бути прийняте.
користувач2180613

Вказівки на кшталт "Геттери - це зло" існують, оскільки багато людей розглядають екземпляри класу як структури даних (Мішок даних, який потрібно передавати навколо). Це не об’єктно-орієнтоване програмування. Об'єкти - це групи функціональності / поведінки, які фактично забезпечують певну роботу. Обсяг роботи визначається відповідальністю. Ця робота очевидно створить об'єкти, які повертаються з методів тощо. Якщо ваш клас насправді виконує значущу роботу, яку ви можете чітко визначити за межами "представляти об'єкт даних X з атрибутами Y і Z", то ви на правильному шляху . Я б не стрес від цього.
Джеремі

Щоб розширити цю точку, коли ви використовуєте багато учасників / запитувачів, це означає, що у вас є десь великий метод, який оркеструє все, що Фоулер називає сценарієм транзакцій. Перш ніж почати користуватися геттером, запитайте себе ... чому я запитую ці дані від об'єкта? Чому ця функціональність не є методом на об'єкті? Якщо цей клас не несе відповідальність за реалізацію цієї функціональності, то продовжуйте користуватися геттером. Книга [ Refactoring ] ( 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. Коли ви це робите, вам слід уточнити, чи відповідає цей член вашому класу, тому що якщо це не так, якийсь клієнт може спробувати приєднати вас до цього класу, використовуючи вас, щоб отримати його перед його використанням. Це важливо, тому що тепер те, що ви повертаєтеся, завжди повинно підтримувати це використання.

Є багато випадків, коли це просто не викликає побоювань. Колекції можуть ігнорувати це просто тому, що вони призначені дружити з усім, що їх використовує. Подібно цінні об’єкти дружні з усіма. Але якщо ви написали утиліту, що підтверджує адресу, яка вимагала від співробітника об'єкта, щоб він вилучив адресу, а тоді просто попросіть адресу, добре ви будете сподіватися, що і працівник, і адреса походять з однієї бібліотеки.


Вільні інтерфейси не є винятком для LOD через певне поняття дружелюбності .... У плавному інтерфейсі кожен із методів ланцюга викликається одним і тим же об'єктом, тому що всі вони повертаються. settings.darkTheme().monospaceFont()- це просто синтаксичний цукор дляsettings.darkTheme(); settings.monospaceFont();
Jonah

3
@Jonah Повернення себе - це максимальна доброзичливість. І не всі вільні інтерфейси. Багато iDSL також вільно володіють різними типами і змінюють доступні методи в різних точках. Розглянемо jOOQ , крок будівельник , майстер будівельник , і мій власний кошмарні будівельник монстра . Вільно - це стиль. Не зразок.
candied_orange

2

Крім класів, які були призначені спеціально для повернення об'єктів [...]

Я не зовсім розумію цей аргумент. Будь-який об’єкт може повернути об'єкт у результаті виклику повідомлення. Особливо, якщо ви думаєте в «все як об’єкт».

Класи, однак, є єдиними об'єктами, які можуть створювати нові об'єкти подібного типу, надсилаючи їм "нове" повідомлення (або часто замінюючи новим ключовим словом на найбільш поширених мовах OOP)


У будь-якому випадку, стосовно вашого запитання я б дуже підозріло ставився до об'єкта, який повертає одну з його властивостей для того, щоб використовувати його в іншому місці. Можливо, цей об’єкт витікає інформацію про стан або інформацію про його реалізацію.

І ви не хочете, щоб об'єкт C знав про деталі реалізації B. Це небажана залежність від об'єкта A з точки зору об'єкта C.

Тож ви можете запитати себе, чи знаючи А, не знає занадто багато для роботи, яку він повинен виконати, незалежно від LOD.

Є випадки, коли це може бути законним, наприклад, запит об’єкта колекції для одного з його предметів є доцільним і не порушує жодного правила Demeter. З іншого боку, запитувати об’єкт Book для об'єкта SQLConnection є ризикованим і його слід уникати.

LOD існує тому, що багато програмістів, як правило, запитують об'єкти для інформації, перш ніж виконувати роботу з неї. LOD є для того, щоб нагадати, що іноді (якщо не завжди) ми просто надмірно ускладнюємо речі, бажаючи, щоб наші об'єкти робили занадто багато. Іноді нам просто потрібно попросити інший об’єкт, щоб він зробив нам роботу. LOD є для того, щоб змусити задуматися двічі про те, де ми розміщуємо поведінку в своїх об'єктах.


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. Здається, повернення значень суперечить цьому.
користувач2180613

1
Я вважаю, що це правда, але це не відповідає на питання.
користувач2180613

0

Залежить від того, що ти робиш. Якщо ви виставляєте члена свого класу, коли це нікому не належить, що це член вашого класу або який його тип, це погано. Якщо потім змінити тип цього члена та тип функції, що повертає члена, і кожен повинен змінити свій код, це зло.

З іншого боку, якщо в інтерфейсі вашого класу зазначено, що він постачає екземпляр відомого класу A, це добре. Якщо вам пощастило і ви можете це зробити, поставивши члена свого класу, добре вам. Якщо ви змінили цього члена з "А" на "Б", тоді вам потрібно буде переписати метод, який повертає "А", очевидно, тепер йому доведеться сконструювати його та заповнити доступними даними, а не лише однолінійним поверненням член. Інтерфейс вашого класу буде незмінним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.