Закон Деметера ( LoD ) или принцип наименьшего знания — это руководство по проектированию программного обеспечения , в частности объектно-ориентированных программ . В своей общей форме LoD — это частный случай слабой связанности . Руководство было предложено Яном Холландом в Северо-Восточном университете в конце 1987 года [1] , и следующие три рекомендации служат кратким резюме: [2]
Основная идея заключается в том, что данный объект должен предполагать как можно меньше о структуре или свойствах чего-либо еще (включая его подкомпоненты), в соответствии с принципом « сокрытия информации ». Его можно рассматривать как следствие принципа наименьших привилегий , который предписывает, чтобы модуль обладал только информацией и ресурсами, необходимыми для его законного назначения.
Он так назван по своему происхождению из проекта Demeter, адаптивного программирования и аспектно-ориентированного программирования . Проект был назван в честь Деметры , «матери-распределителя» и греческой богини сельского хозяйства , чтобы обозначить философию программирования снизу вверх, которая также воплощена в самом законе. [3] [ необходим неосновной источник ]
Закон датируется 1987 годом, когда его впервые предложил Ян Холланд, работавший над проектом Demeter. Этот проект стал местом рождения многих принципов аспектно-ориентированного программирования (АОП).
Цитата в одном из фрагментов проекта, по-видимому, проясняет происхождение названия: [4]
Деметра
Греческая богиня земледелия.
Проект Demeter был назван в честь Demeter, потому что мы работали над языком описания оборудования Zeus и искали инструмент для упрощения реализации Zeus. Мы искали название инструмента, связанное с Zeus, и выбрали сестру Zeus: Demeter.
Позже мы продвигали идею о том, что разработка ПО в стиле Demeter заключается в росте ПО, а не в его создании. Мы ввели концепцию плана роста, который по сути является последовательностью все более и более сложных диаграмм классов UML.
Планы роста полезны для постепенного создания систем.
Объект a
может запросить услугу (вызвать метод) экземпляра объекта b
, но объект a
не должен «проникать» через объект b
, чтобы получить доступ к еще одному объекту, c
, чтобы запросить его услуги. Это означало бы, что объект a
неявно требует большего знания b
внутренней структуры объекта.
Вместо этого b
интерфейс должен быть изменен при необходимости, чтобы он мог напрямую обслуживать a
запрос объекта, распространяя его на любые соответствующие подкомпоненты. В качестве альтернативы a
можно иметь прямую ссылку на объект c
и делать запрос непосредственно к нему. Если закон соблюден, только объект b
знает свою собственную внутреннюю структуру.
Более формально, закон Деметера для функций требует, чтобы метод m
объекта a
мог вызывать только методы следующих видов объектов: [5]
a
сам по себе;m
параметры;m
;a
атрибуты;a
в области действия m
.В частности, объект должен избегать вызова методов объекта, возвращаемого другим методом. Для многих современных объектно-ориентированных языков, которые используют точку в качестве идентификатора поля, закон можно сформулировать просто как «используйте только одну точку». [6] То есть, код a.m().n()
нарушает закон там, где a.m()
этого не происходит. В качестве аналогии , когда кто-то хочет, чтобы собака шла, он не командует ногам собаки идти напрямую; вместо этого он командует собаке, которая затем командует своим собственным ногам.
Преимущество следования закону Деметера заключается в том, что полученное программное обеспечение, как правило, более удобно для сопровождения и адаптации . Поскольку объекты меньше зависят от внутренней структуры других объектов, реализацию объектов можно изменять без переделки их вызывающих объектов.
Basili et al. [7] опубликовали экспериментальные результаты в 1996 году, предполагая, что более низкий Response For a Class (RFC, количество методов, потенциально вызываемых в ответ на вызов метода этого класса) может снизить вероятность ошибок программного обеспечения . Следование закону Деметера может привести к более низкому RFC. Однако результаты также предполагают, что увеличение Weighted Methods per Class [8] (WMC, количество методов, определенных в каждом классе) может увеличить вероятность ошибок программного обеспечения. Следование закону Деметера также может привести к более высокому WMC.
Многослойную архитектуру можно считать систематическим механизмом для реализации закона Деметера в программной системе. В многослойной архитектуре код внутри каждого слоя может вызывать только код внутри слоя и код внутри следующего слоя ниже. «Пропуск слоя» нарушил бы многослойную архитектуру.
Хотя LoD повышает адаптивность программной системы, это может привести к необходимости написания множества методов-оболочек для распространения вызовов к компонентам; в некоторых случаях это может привести к заметным временным и пространственным издержкам. [7] [9] [10]
На уровне методов LoD приводит к узким интерфейсам, предоставляя доступ только к той информации, которая ему необходима для выполнения его работы, поскольку каждому методу необходимо знать о небольшом наборе методов тесно связанных объектов. [11] С другой стороны, на уровне классов, если LoD используется неправильно, могут быть разработаны широкие (т. е. увеличенные) интерфейсы, требующие введения множества вспомогательных методов. [9] [10] Это происходит из-за плохого дизайна, а не из-за LoD как такового. Если используется метод-обертка, это означает, что объект, вызываемый через обертку, должен был быть зависимостью в вызывающем классе.
Одним из предлагаемых решений проблемы расширенных интерфейсов классов является аспектно-ориентированный подход [12] , где поведение метода определяется как аспект на высоком уровне абстракции. Широкие интерфейсы управляются через язык, который определяет реализации. Как стратегия обхода, так и адаптивный посетитель используют только минимальный набор классов, участвующих в операции, а информация о связях между этими классами абстрагируется.
Как и ожидалось, чем больше WMC, тем больше вероятность обнаружения неисправности.
эффектом этого является то, что если вы соответствуете LoD, то, хотя это может значительно повысить удобство обслуживания и "адаптивность" вашей программной системы, вам также придется писать множество небольших методов-оболочек для распространения вызовов методов на ее компоненты (что может добавить заметные временные и пространственные издержки).
Недостатком, конечно, является то, что в итоге вы пишете много маленьких методов-оберток, которые делают очень мало, но делегируют обход контейнера и тому подобное. Компромисс по стоимости заключается между этой неэффективностью и более высокой степенью связанности.
Более простое обслуживание программного обеспечения, меньшая связанность между методами, лучшее сокрытие информации, более узкие интерфейсы, методы, которые легче использовать повторно, и более простые доказательства корректности с использованием структурной индукции.
Адаптивный метод инкапсулирует поведение операции в одном месте, тем самым избегая проблемы рассеивания, но также абстрагируется от структуры класса, тем самым избегая проблемы запутывания.