Глубокий классовый состав и Закон Деметры

Вечер. У меня возникли проблемы с поиском подходящего шаблона проектирования для некоторых ситуаций с глубокой композицией. Позвольте мне привести пример.

Допустим, у нас есть класс типа Corporation, который имеет много классов типа Subsidiary, которые имеют много классов типа Department, которые по типу содержат множество классов типа Unit, которые, в свою очередь, содержат множество классов типа Employee.

Теперь предположим, что вариант использования заключается в подсчете количества сотрудников для каждой корпорации. Я мог бы перебирать каждую корпорацию, снова зацикливаться для каждой дочерней компании и т. д. и т. д., что привело бы к вложенному циклу глубиной 4 уровня. Кроме того, я бы нарушил Закон Деметры, сославшись на свою цепочку классов несколькими уровнями ниже, что-то, что настолько тесно связано, что сломалось бы в тот же момент, когда я изменил бы свою цепочку.

Еще одна вещь, которую я мог бы сделать, это добавить тонны (хорошо, может быть, не тонны, но несколько) ссылок на ярлыки. Например, корпорация может сама ТАКЖЕ содержать список сотрудников, в результате чего никогда не придется проходить цепочку, чтобы подсчитать их. Таким образом, классы менее тесно связаны (но так ли это?), и теперь проблема заключается в том, как синхронизировать список сотрудников как для корпорации, так и для подразделения. Я мог бы использовать шаблон Observer, чтобы обновлять их, я полагаю, но я действительно чувствую, что что-то ужасно неправильно с этой идеей, или, по крайней мере, я действительно не использую лучшее решение.

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

Спасибо.


person ctsag    schedule 30.08.2013    source источник


Ответы (2)


Я не совсем понимаю второй вопрос, но я отвечаю на первый вопрос.

Поскольку Закон Деметры гласит, что каждая сущность должна иметь наименьшее знание о других единицах

Поэтому, используя этот принцип в своем дизайне

class Corporation{

    //All the stuff about corporation

    //Don't ask for what's inside corporation
    public int countEmployees(){
        //will apply the logic needed to calculate
    }

}

Лучший клиентский код с Законом Деметры:

corporationInstance.countEmployees(); //let the corporation handle how to count and not expose inner details

Без закона Деметры

corporationInstace.getSubsidiaries().getSomethingElse()..... //exposing the inner details of class which creates a chain that is bad.

ОБНОВЛЕНИЕ:

Используя вышеуказанное решение, вы можете углубиться в любую глубину, создав метод countEmployees() внутри Subsidiaries и в Unit по мере необходимости. Здесь нет смысла нарушать инкапсуляцию или использовать паттерн Observer.

Примените Tell Don't не спрашивайте принцип, как вы сами указали в комментарии, и делегируйте ответственность за расчет фактических сотрудников в классе, который содержит сотрудников.

Department - > uses count method on subsidiaries to add their count
Subsidiaries - > uses Units to count
Unit - > Uses employees to count
person Narendra Pathai    schedule 30.08.2013
comment
Это то, что я предлагал, упоминая методы быстрого доступа. Однако, если бы я реализовал countEmployees(), мне понадобился бы, скажем, массив сотрудников, верно? По сути, это должен быть тот же массив сотрудников, который будет содержать класс Unit. И мне нужно будет синхронизировать их, возможно, с шаблоном Observer. Вы тоже это предлагаете? - person ctsag; 31.08.2013
comment
Нет, чьей собственностью являются сотрудники? Это из Единицы? Затем вам нужно будет создать методы быстрого доступа для дочерних компаний, которые, в свою очередь, будут вызывать метод быстрого доступа подразделения, в котором будут сотрудники. Вам не нужно извлекать сотрудников из класса единиц. - person Narendra Pathai; 31.08.2013
comment
Таким образом, каждый метод в последовательной цепочке классов будет содержать метод countEmployees(), который вызовет метод counteEmployees() следующего класса. Я полагаю, это имеет смысл, поскольку я могу подсчитывать сотрудников на любом уровне цепочки, который захочу. Мне нравится этот подход, спасибо за ваш вклад, Нарендра. Кстати, как бы вы назвали этот принцип? Это Рассказывай, не спрашивай? - person ctsag; 31.08.2013
comment
Я согласен с этим подходом. Корпорация может провести подсчет, попросив каждую дочернюю компанию подсчитать и суммировать полученные результаты, и так далее. Но таким образом, если у вас есть особый случай в мелочах, например, директор одной дочерней компании, который имеет более двух подразделений, эта дочерняя компания может нести ответственность за то, чтобы он учитывался ровно один раз в своем методе подсчета. - person Mark Bailey; 31.08.2013
comment
Да, это то, что гласит Закон Деметры, вам не придется увольнять сотрудников. Также помните о принципе ответственности, сотрудники должны быть частью класса, который действительно несет ответственность за них :) - person Narendra Pathai; 31.08.2013
comment
misko.hevery.com/code-reviewers-guide/ это хороший пост на ту же тему - person Narendra Pathai; 31.08.2013
comment
Да, это скажи, не спрашивай. captaindebug.com/2012/03 / - person Narendra Pathai; 31.08.2013
comment
Я обновил ответ, включив правильное объяснение со ссылкой. - person Narendra Pathai; 31.08.2013
comment
Спасибо за ответы, ребята. Однако я думаю, что наблюдение Марка весьма интересно. Если класс A на графике делегирует свой счет следующему классу, скажем, (каждому) классу B, это означает, что, когда класс A содержит несколько классов типа B, у него нет возможности узнать, учитывается ли сотрудник дважды (потому что, как отметил Марк, сотрудник может быть назначен на два или более подразделений.Для этого классу A потребуется собрать набор всех сотрудников, чтобы исключить дубликаты). Итак, вместо делегирования countEmployees(), возможно, следует делегировать listEmployees(), не так ли? - person ctsag; 31.08.2013
comment
Никакое делегирование listEmployees() не нарушит инкапсуляцию. Какие еще вещи выполняет Юнит. Я думаю, что Unit должен быть свойством Employee, поскольку сотрудник также может быть частью нескольких блоков. - person Narendra Pathai; 31.08.2013
comment
Как listEmployees() нарушит инкапсуляцию? Если бы я назвал его getEmployees(), это был бы просто геттер для частного свойства типа Employee[]. Так что же в этом плохого с точки зрения инкапсуляции? Класс Unit может выполнять ряд других действий, обеспечивать затраты, статистику и т. д., т. е. он не обязательно должен быть фиктивным классом. Кроме того, проблема с дублирующимся сотрудником не обязательно должна ограничиваться уровнем подразделения, она может дойти до самого верха, сотрудник может быть также назначен в два разных отдела (даже в дочерние компании, если вы решите игнорировать юридические последствия). . - person ctsag; 31.08.2013
comment
Насколько я понимаю, может быть отдел кадров, который хранит рекомендации всех сотрудников и знает, сколько сотрудников существует. Теперь вызов о закреплении сотрудника за отделом тоже будет проходить через отдел кадров и так далее. Теперь в этом случае HR выделит этого сотрудника в этот отдел. Это просто моя точка зрения на это. Это нарушает инкапсуляцию, поскольку вы передаете весь список вызывающему абоненту, и вызывающий абонент может добавить () удалить () что-либо из него. Теперь, если это принято, вы также можете использовать listEmployees(). Правила тоже можно нарушать.. :) - person Narendra Pathai; 31.08.2013
comment
Нет, я понимаю вашу точку зрения на инкапсуляцию, но это можно легко исправить, вернув неизменяемый список. Я прочитал вашу ссылку на Скажите, не спрашивайте принцип, и мне было интересно, почему getAllItems() (эквивалент listEmployees() в нашем случае) - плохая идея. В конце концов, стандартный вид корзины показывает не только итоги и суммы, но и показывает, какие товары вы покупаете, не так ли? Итак, если перечисление товаров не входит в обязанности объекта ShoppingCart, то чья это обязанность? - person ctsag; 31.08.2013
comment
Да, вы можете запросить все элементы в корзине, но вы можете добиться того же, используя toString() для перечисления всех элементов для отображения где-либо. Тем не менее, совершенно справедливо нарушать правила, поскольку они всего лишь рекомендации. Также я хотел бы заявить, что моделировать объекты реального мира в объектно-ориентированной манере сложно. - person Narendra Pathai; 31.08.2013

Допустим, вы хотите отправить электронное письмо клиенту по ссылке или кнопке. Вы можете написать это как customer.getSomeParticularContactInfo(addressType).sendEmail() или customer.sendEmail(), который затем (внутри Customer) вызывает getSomeParticularContactInfo("primary").sendEmail().

Вы находитесь на неправильном пути. Это нарушает единую ответственность, я имею в виду, что объекту «Клиент» не нужно знать, как отправлять электронную почту, объект «Клиент» отвечает только за то, как предоставить адрес электронной почты, принадлежащий клиенту. Поэтому для этой функциональности вам нужно создать другой интерфейс, такой как Notifier и EmailNotifier, который реализует Notifier. После этого вы вызовете EmailNotifier.notify(клиент)

person fureszpeter    schedule 05.07.2015