Гостевая статья Закон Деметры, практический пример

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

Итак, вчера была моя очередь представлять, перечисляя основные моменты из части чтения на этой неделе (глава 5), делясь некоторыми мыслями и, надеюсь, подстегивая разговор. Авторы начинают с того, что говорят о Законе Деметры, но не очень хорошо объясняют его и не называют его гораздо более понятным названием - Принцип Наименьшего Знания.

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

pirates.jpg



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

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

Код:
var paid = new Customer().GetWallet().GetPayment();


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

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

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

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

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

Код:
public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string SSN { get; set; }
    public decimal Salary { get; set; }
    public DateTime HireDate { get; set; }
    public DateTime? TerminationDate { get; set; }
    public bool IsActive { get { return !TerminationDate.HasValue; } }
}

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

Код:
public class Celebration
{
    public Employee Employee { get; private set; }
   
    public Celebration(int empId)
    {
        // somehow we load the employee's data
        Employee = dbContext.Students.Get(empId);
    }

    public void PurchaseCake()
    {
        // order a costco cake
    }
    public void PurchaseDeluxeCake()
    {
        // order a dairyqueen icecream cake
    }
    public void SendCard()
    {
        // fire off a hallmark card
    }
}

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

Код:
var celebrate = new Celebration(1234);

if (celebrate.Employee.IsActive
    && celebrate.Employee.HireDate.Date == DateTime.Now.Date)
{
    if (celebrate.Employee.HireDate.Year + 9 < DateTime.Now.Year)
        celebrate.PurchaseDeluxeCake();  // 10 years gets you the big cake
    else if (celebrate.Employee.HireDate.Year < DateTime.Now.Year)
        celebrate.PurchaseCake();
    else
        return;

    celebrate.SendCard();
}


Но подождите секунду. Почему какой-то фрагмент кода, который проверяет празднование, должен иметь доступ к Employee классу? Скажите, что это не код, и менеджеру необходимо вручную проверить записи сотрудников, чтобы увидеть дату их найма. Скажем , когда - нибудь все это становится переложено на кого - то другое , чем менеджер, как комитет партийного планирования новообразованного. Разумно ли, что просто для того, чтобы отправить вам карточку и заказать торт, человеку потребуется доступ ко всей вашей записи сотрудника, включая номер зарплаты и номер социального страхования? Noooo. Нет это не так.

Принцип наименьших знаний заставляет нас переосмыслить степень доступа класса A через класс B к классам C, D и E. Другими словами, нам следует скрыть любые подробности о Employee классе, которые не нужно раскрывать - даже скрыть тот факт, что там вообще есть экземпляр Employee.

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

Код:
public class Celebration
{
    private Employee employee;
   
    public Celebration(int empId)
    {
        // somehow we load the employee's data
        employee = dbContext.Students.Get(empId);
    }
   
    public bool IsEmployeeAnniversary()
    {
        return employee.IsActive
            && employee.HireDate.Date < DateTime.Now.Date;
    }
   
    public void PurchaseCake()
    {
        if (employee.HireDate.Year + 9 < DateTime.Now.Year)
            // send for a dairyqueen cake
        else
            // send for a costco cake
    }
    public void SendCard()
    {
        // fire off a hallmark card
    }
}


Это имеет множество положительных эффектов на кодовую базу, в том числе:
  • Упрощение логики в вызывающей программе, которая больше не должна выяснять, как решить, является ли это годовщиной сотрудника, просто вызовите методы и позвольте другому классу делать работу.
  • Если тот же код, что и ниже, вызывается где-либо еще, то вышеприведенный код также изменяет вверх по базе кода, сохраняя логику в одном месте.
  • И если логика внутри Celebrationкода изменится - может быть, какой-то другой критерий определит годовщину, или 20 лет работы в компании принесут вам пирог Cheesecake Factory - тогда все, что в кодовой базе выполнит код, подобный приведенному ниже, выиграло ». не нужно трогать Ницца!
Код:
var celebrate = new Celebration(1234);

if (celebrate.IsEmployeeAnniversary())
{
    celebrate.PurchaseCake();
    celebrate.SendCard();
}

Спасибо Деметре, за полезные законы. Я должен тебе торт и открытку.

Источник:
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!