Рейки: Закон плутанини Деметра


13

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

Вони вважають, що викликати щось подібне в контролері погано (і я згоден)

@street = @invoice.customer.address.street

Запропоноване ними рішення полягає в наступному:

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

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

http://www.dan-manges.com/blog/37

У публікації щоденника головний приклад

class Wallet
  attr_accessor :cash
end
class Customer
  has_one :wallet

  # attribute delegation
  def cash
    @wallet.cash
  end
end

class Paperboy
  def collect_money(customer, due_amount)
    if customer.cash < due_ammount
      raise InsufficientFundsError
    else
      customer.cash -= due_amount
      @collected_amount += due_amount
    end
  end
end

У публікації блогу зазначається, що хоча customer.cashзамість нього є лише одна крапка customer.wallet.cash, цей код все ще порушує закон про деметер.

Зараз у методі Paperboy collection_money у нас немає двох крапок, у нас просто є "customer.cash". Чи вирішила ця делегація нашу проблему? Зовсім ні. Якщо ми подивимось на поведінку, папірець все ще потрапляє безпосередньо у гаманець клієнта, щоб отримати гроші.

EDIT

Я повністю розумію і погоджуюся, що це все-таки порушення, і мені потрібно створити метод у Walletcall call, який обробляє платіж за мене, і що я повинен викликати цей метод всередині Customerкласу. Чого я не розумію, це те, що згідно з цим процесом, мій перший приклад все ще порушує Закон Деметера, оскільки Invoiceдосі досягає прямо, Customerщоб отримати вулицю.

Хтось може допомогти мені очистити плутанину. Я шукав останні 2 дні, намагаючись дозволити цій темі зануритися, але це все ще заплутано.


2
подібне питання тут
thorsten müller

Я не думаю, що другий приклад (папірець) із блогу порушує закон про деметер. Це може бути поганий дизайн (ви припускаєте, що клієнт заплатить готівкою), але це НЕ є порушенням Закону про деметер. Не всі помилки проектування спричинені порушенням цього Закону. Автор розгублений ІМО.
Андрес Ф.

Відповіді:


24

Ваш перший приклад не порушує закон Деметра. Так, з кодом, який він є, кажучи, @invoice.customer_streetщо трапляється отримувати те саме значення, що і гіпотетичне @invoice.customer.address.street, але на кожному кроці обходу значення, що повертається, визначається запитуваним об'єктом - це не те, що "папір досягає клієнтський гаманець ", це те, що" папір вимагає від замовника готівку, а замовник отримує готівку зі свого гаманця ".

Коли ви говорите @invoice.customer.address.street, ви припускаєте знання клієнта та адреси внутрішніх справ - це погано. Коли ви говорите @invoice.customer_street, ви запитуєте invoice: "Ей, я хотів би, щоб вулиця замовника, ви вирішили, як це отримати ". Потім замовник каже на свою адресу: "Ей, мені б хотілося, щоб ваша вулиця, ви вирішили, як її отримати ".

Спрямованість Деметри є НЕ «ви ніколи не можете знати значення з об'єктів далеко в графі від вас", це замість того, щоб "ви самі не пройти далеко уздовж графа об'єкта для отримання значень.

Я згоден, це може здатися тонким розрізненням, але врахуйте це: у коді, сумісному з Деметером, скільки коду потрібно змінити, коли внутрішнє представлення addressзмін? А як щодо коду, що не відповідає Demeter?


Це саме те пояснення, яке я шукав! Дякую.
user2158382

Дуже хороше пояснення. У мене виникають запитання: 1) Якщо об'єкт рахунка-фактури хоче повернути клієнтові об’єкт клієнту рахунку-фактури, це не обов'язково означає, що це той самий об’єкт клієнта, який він зберігає всередині країни. Це може бути просто об'єкт, створений на льоту, з метою повернення клієнтові приємного пакетованого набору даних з кількома значеннями в ньому. Використовуючи представлену вами логіку, ви говорите, що рахунок-фактура не може мати поле, яке представляє більше ніж одну інформацію. Або я щось пропускаю.
zumalifeguard

2

Перший приклад і другий насправді не дуже однакові. У той час як перший говорить про загальні правила "однієї крапки", другий розповідає більше про інші речі в дизайні ОО, особливо " Скажи, не питати "

Делегування - це ефективна методика уникнення порушень Закону про Демет, але лише для поведінки, а не для ознак. - З другого прикладу, блог Дана

Знову ж таки, " лише для поведінки, а не для атрибутів "

Якщо ви запитуєте атрибути, ви повинні запитати . "Гей, хлопче, скільки грошей у тебе в кишені? Покажи мені, я оціню, чи ти можеш це заплатити". Це неправильно, жоден торговий службовець не поводитиметься так. Натомість вони скажуть: "Будь ласка, платіть"

customer.pay(due_amount)

Зобов'язання замовника буде оцінювати, чи повинен він платити і чи може він платити. І завдання канцелярії закінчено після того, як сказати клієнту заплатити.

Отже, чи доводить другий приклад, що перший неправильний?

На мою думку. Ні , поки:

1. Ви робите це з самообмеженням.

Хоча ви можете отримати доступ до всіх атрибутів замовника за @invoiceдопомогою делегування, у звичайних випадках це рідко потрібно.

Подумайте про сторінку, де відображається рахунок-фактура в додатку Rails. Зверху з'явиться розділ, де можна побачити деталі замовника. Отже, в шаблоні рахунків-фактур ви будете кодувати так?

#customer-info
  = @invoice.customer_name
  = @invoice.customer_address
  ....

Це неправильно і неефективно. Кращий підхід

#customer-info
  = render partial: 'invoice_header_customer', 
           locals: {customer: @invoice.customer}

Тоді нехай клієнт частково обробляє всі атрибути, належать замовнику.

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

= @invoice.customer_name

2. Немає подальших дій залежно від виклику цього методу.

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


0

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

class Wallet
  attr_accessor :cash
  def withdraw(amount)
     raise InsufficientFundsError if amount > cash
     cash -= amount
     amount
  end
end
class Customer
  has_one :wallet
  # behavior delegation
  def pay(amount)
    @wallet.withdraw(amount)
  end
end
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.pay(due_amount)
  end
end

Тому я думаю, що ваша друга довідка дає більш корисну рекомендацію.

Ідея "єдиної точки" є частковим успіхом, оскільки вона приховує деякі глибокі деталі, але все ще збільшує зв'язок між окремими компонентами.


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

0

Здається, що Ден вивів його приклад із цієї статті: Paperboy, Wallet та Law of Demeter

Закон Деметера Метод об’єкта повинен застосовувати лише методи таких видів об'єктів:

  1. себе
  2. його параметри
  3. будь-які об'єкти, які він створює / створює
  4. її прямі складові об'єкти

Коли і як застосовувати закон Деметера

Отже, ви добре розумієте закон і його переваги, але ми ще не обговорювали, як визначити місця в існуючому коді, де ми можемо його застосувати (і так само важливо, де НЕ застосовувати його ...)

  1. Закуповані заяви "отримати" - Перше, найочевидніше місце для застосування Закону Деметера - це місця коду, які мають повторювані get() заяви,

    value = object.getX().getY().getTheValue();

    так, якби коли нашу канонічну особу для цього прикладу витягнув поліцейський, ми можемо побачити:

    license = person.getWallet().getDriversLicense();

  2. багато 'тимчасових' об’єктів - Наведений вище приклад ліцензії не був би кращим, якби код виглядав,

    Wallet tempWallet = person.getWallet(); license = tempWallet.getDriversLicense();

    це рівнозначно, але важче виявити.

  3. Імпорт багатьох класів - у проекті Java, над яким я працюю, у нас є правило, що ми імпортуємо лише класи, які ми фактично використовуємо; ти ніколи не бачиш чогось подібного

    import java.awt.*;

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

Я розумію, що ваш приклад є у Ruby, але це має стосуватися всіх мов OOP.

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