Страницы

Поиск по вопросам

суббота, 4 января 2020 г.

Чистый код: Непонятный закон Деметры

#ооп #проектирование


Сейчас читаю книгу ЧИстый код на небольшой главе про закон Деметры. До этого с ним
сталкивалась в паттернах от O'reilly (HeadFirst) и там было немного и понятно. После
чистого кода - в голове кавардак. Есть пара вопросов:

1) Мартин делит объекты на объекты и структуры данных. Что тут подразумевается под
структурой? Struct (в c# к примеру) или в целом объект класса в котором кроме открытых
полей (или свойств) нету больше ничего (методов, например)?

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

3) Этот вопрос поднимала на устное обсуждение в другими разрабами (мы не джуны, но
и не "крутыши") и также возникло непонимание самой сути этого закона, зачем его соблюдать,
что является главной причиной: или база для дальнейших легковносимых изменений, или
сокрытие внутренней структуры объекта, или даже легкое предотвращение и обработка NullReferenceException?
Все понимают по разному.

4) Нарушается ли закон при разделении методов "плохих" на много маленьких?

Было

public class Class1
{
    public String DoSmth()
    {
        //Вроде как закон нарушен, потому что вызываем метод у вернувшегося значения
        return MethodClass1().DoSmth();
    }

    public Class2 MethodClass1()
    {
        return new Class2();
    }
}

public class Class2
{
    public String DoSmth()
    {
        String res2 = this.MethodClass2();
        return res2;
    }

    public String MethodClass2()
    {
        return "test";
    }
}


Стало

public class Class1
{
    public String DoSmth()
    {
        //теперь тут вызываются только методы того же класса
        Class2 res1 = MethodClass1();
        return this.MethodClass1_2(res1);
    }

    public Class2 MethodClass1()
    {
        return new Class2();
    }

    public String MethodClass1_2(Class2 val)
    {
        return val.DoSmth();
    }
}


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


Ответы

Ответ 1



В книге "Программист-прагматик" (Хант, Томас) закон Деметера (такой кривой перевод) сведён к короткому высказыванию: Минимизируйте связывание между модулями Пример из книги на C++ class Demeter { private: A* a; int func(); public: void example(B& b); } void Demeter::example(B& b) { int f = func(); // 1 b.invert(); // 2 a = new A(); a->setActive(); // 3 C c; c.print(); // 4 } Закон Деметры для функций гласит, что любой метод некоторого объекта может обращаться только к методам принадлежащим: самим себе любым параметрам, переданным в метод любым создаваемым им объектам любым непосредственно содержащимся объектам компонентов Исходя из этого можно дать ответ на ваш вопрос №3: уменьшение связанности облегчает модификацию и сопровождение кода, облегчает написание тестов. На практике это означает, что придётся создавать большое количество методов-оболочек, которые просто направляют запрос далее к делегату. Эти методы-оболочки влекут за собой расходы, ухудшая производительность и занимая память. Обращая закон Деметры и плотно связывая несколько модулей, можно получить выигрыш в производительности.

Ответ 2



1) Про структуру он там явно пишет: С другой стороны, если ctxt, Options и ScratchDir представляют собой обычные структуры данных, не обладающие поведением, то они естественным образом раскрывают свою внутреннюю структуру, а закон Деметры на них не распространяется. Поведение никак не скрыто, если вызывающий код обращается напрямую к данным, а не через get/set (это и есть простая структура, как в Си, например). В то же время, если обращение идёт через get/set ещё не значит, что там скрыто какое-то поведение (оно может быть там скрыто, но не факт) и поэтому: Применение функций доступа затрудняет ситуацию. 2) И про гибриды, вроде доступно: Гибриды содержат как функции для выполнения важных операций, так и открытые переменные или открытые методы чтения/записи, которые во всех отношениях делают приватные переменные открытыми. По-моему, сокрытие поведения и является ключевым фактором. Если есть какое-то поведение и оно скрыто, то это объект, если поведения нету, а просто открытые данные, то это структура, а если есть и скрытое поведение и открытые данные - вот вам и гибрид.

Ответ 3



Отвечу на 4-ый вопрос. Если "Было" и "Стало" оставить такими, в каком виде они у вас, то закон нарушается в обоих случаях. Какие тут есть "нарушения": Class1 по сути является оберткой для Class2 (по крайней мере из того кода, что вы предоставили) - такое поведение должно достигаться наследованием; Class1 очень сильно зависит от Class2 (Почему Class1, который не является фабрикой, занимается созданием экземпляра Class2?). Должно "Стать" так: public class Class2 { public RETURN_TYPE DoSmth() { // Что-то там делается } } public class Class1 { public RETURN_TYPE DoSmth(Class2 val) { /* Тут должна быть какая-то логика, но не тупой возврат, вроде: return val.DoSmth(); */ } } А ещё лучше будет, если экземпляр класса Class2 передать в конструктор класса Class1 и обращаться в методах к объекту класса Class2 через свойство класса Class1 - кстати, этого и требует в данном контексте "закон Деметры". При этом как зависимость надо передавать не сам класс, а интерфейс, который будет реализовывать Class2. На 3-ий вопрос вы сами же отчасти и ответили - код становится легче в сопровождении, поддержке, написании тестов, код становится менее сложным ля понимания, больше возможностей при повторном использовании кода (иначе код будет дублироваться), снижается зависимость классов друг от друга.

Комментариев нет:

Отправить комментарий