Закон Деметры может быть один из самых определенных, полезных и сжато написанных правил разработки программного обеспечения объектно-ориентированной истории. Это также может быть одной из наиболее часто игнорируемых вещей в нашей профессии — кроме сроков.
Давайте глубоко погрузимся в то, что он говорит, что это на самом деле означает, и как подчиняться этому в букве и духе.
Почему мы должны подчиняться закону Деметры?
В объектно-ориентированном программировании существуют хорошо известные абстрактные концепции, такие как инкапсуляция, сплоченность и связывание, которые теоретически могут быть использованы для создания четких конструкций и хорошего кода. Хотя это все очень важные концепции, они просто недостаточно прагматичны, чтобы быть непосредственно полезными для развития. Нужно интерпретировать эти понятия, и с этим они становятся несколько субъективными и начинают зависеть от опыта и знаний людей.
То же самое можно сказать и о других концепциях, таких как Принцип Единой Ответственности или Принцип Открытого / Закрытого и т. Д. Они допускают очень широкий запас толкования, поэтому практичность, прямая полезность, поэтому уменьшается.
Гениальность Закона Деметры происходит от краткого и точного определения, которое позволяет непосредственно применять его при написании кода, в то же время почти автоматически гарантируя надлежащую инкапсуляцию, сплоченность и слабую связь. Авторам Закона удалось взять эти абстрактные понятия и объединить их суть в четкий набор правил, универсально применимых к объектно-ориентированному коду.
Так что это говорит?
Закон в его первоначальной форме сформулирован так:
Для всех классов C и для всех методов M, связанных с C, все объекты, которым M отправляет сообщение, должны быть
- Объекты аргумента М, включая объект самообслуживания или
- Объекты переменных экземпляра C
(Объект, созданный M, или функциями или методами, которые вызывает M, и объекты в глобальных переменных рассматриваются как аргументы M.)
В первые дни объектно-ориентирования объекты должны были «отправлять сообщения» друг другу. Вот как они общались. Таким образом, термин «объекты, которым M отправляет сообщение» примерно переводится как «объекты, используемые M», или в более практическом определении «объекты, для которых M вызывает метод».
Вы могли заметить, что в скобках после двух правил есть несколько очень важных дополнительных моментов. Они кажутся разъяснениями, упомянутыми просто мимоходом, но на самом деле они являются дополнительными правилами. Итак, давайте переформулируем весь Закон, чтобы все пункты стояли независимо друг от друга:
Для всех классов C и для всех методов M, связанных с C, все объекты, которым M отправляет сообщение, должны быть:
self
(this
на Java)- Объекты аргумента М
- Переменные экземпляра объектов C
- Объекты, созданные M, или функциями или методами, которые вызывает M
- Объекты в глобальных переменных (
static
поля в Java)
Что это означает?
Ну, закон формулирует то, что нам разрешено делать любым конкретным методом М. Итак, давайте вернемся назад и выясним, что именно запрещает этот закон .
Правило № 4 гласит, что все объекты, созданные во время вызова M, прямо или косвенно, разрешены. Таким образом, запрет должен быть среди объектов, которые уже существовали, когда начался вызов.
Эти уже существующие объекты должны иметь ссылку на них, иначе никто не сможет получить к ним доступ. Поэтому на эти объекты необходимо ссылаться из полей (переменных) других объектов. Правило № 5 разрешает глобальные объекты, поэтому мы оставляем объекты в переменных экземпляра.
Правила № 1, № 2 и № 3, кроме того, допускают «Я», параметры М и все объекты в переменных экземпляра С.
Это означает, что Закон запрещает «отправку сообщения» любому уже существующему объекту, который хранится в переменных экземпляра других классов, если только он не хранится нашим классом или не передается нам в качестве параметров метода.
Примеры
Давайте посмотрим на некоторые примеры Закона в действии:
public final class NetworkConnection {
public void close() {
sendShutdownMessage(); // Allowed?
}
private void sendShutdownMessage() {
...
}
}
Вызов метода sendShutdownMessage()
i, очевидно, разрешен, поскольку он охватывается правилом № 1. Любой метод может быть вызван для текущего объекта.
Как насчет этого:
public final class NetworkConnection {
private Socket socket;
public void close() {
socket.close(); // Allowed?
}
}
Это также разрешено из-за правила № 3 (как socket
и переменная экземпляра этого класса).
Но как насчет этого:
public final class NetworkConnection {
public void send(Person person) {
sendBytes(person.getName().getBytes());
sendBytes(person.getPhoto().getBytes();
...
}
}
Это является нарушением закона Деметры. Метод получил параметр person
, поэтому все вызовы метода для этого объекта разрешены. Тем не менее, любые методы вызова (в данном случае getBytes()
) объекта , возвращаемого либо getName()
или getPhoto()
является не допускается. Предполагая стандартные методы получения, эти объекты уже являются объектами в переменных экземпляра других объектов, поэтому они являются именно теми объектами, к которым у этого метода не должно быть доступа.
Цепные Звонки
Некоторые объяснения Закона концентрируются на так называемых «цепных вызовах», то есть выражениях, которые содержат множественные разыменования. Или, говоря прямо, несколько «точек», как это:
car.getOwner().getAddress().getStreet();
Это почти наверняка является нарушением Закона, поскольку все эти объекты (владелец, адрес), по-видимому, являются уже существующими значениями переменных экземпляра, к которым Закон запрещает доступ.
Тем не менее, нарушением является не вызов, а доступ к определенным объектам. Следующий «переработанный» кодекс все еще нарушает Закон:
Owner owner = car.getOwner();
Address ownerAddress = owner.getAddress();
Street ownerStreet = ownerAddress.getStreet();
Как и выше, объекты в owner
, ownerAddress
и ownerStreet
до сих пор не охвачены ни одним из правил, поэтому никакие методы не должны вызываться для них.
Свободные API
В некоторых толкованиях Закона утверждается, что, поскольку цепные вызовы запрещены, беглые API также запрещены. Свободные API созданы, чтобы позволить синтаксически легко использовать библиотеку или набор классов. Они выглядят так:
Report report = new ReportBuilder()
.withBorder(1)
.withBorderColor(Color.black)
.withMargin(3)
.withTitle("Law of Demeter Report")
.build();
Чтобы определить, разрешена ли такая конструкция, мы просто должны проверить каждый вызов метода, чтобы увидеть, разрешена ли она конкретно. ReportBuilder
Объект создается в этом методе прямо сейчас, поэтому первый вызов метода withBorder(1)
на свеже созданный объект. Это явно разрешено правилом № 4.
Все последующие вызовы метода, включая последний build()
вызов, все используют возвращенные объекты предыдущего вызова. Каково возвращаемое значение всех этих методов? Ну, большинство свободно распространяемых API просто возвращают один и тот же объект снова и снова. Это все еще рассматривается в правиле № 4, потому что строитель — это объект, который был создан в этом коде. Некоторые свободные API используют неизменяемые объекты и каждый раз возвращают новый объект. Но даже если это так, правило № 4 по-прежнему применимо и к косвенно созданным объектам.
Таким образом, Закон Деметры не запрещает беглые API.
Методы «обертки»
Некоторые, в том числе статья в Википедии о законе Деметры , утверждают, что закон подразумевает использование методов «обертки» для обхода вызова методов на сторонних объектах, например:
car.getOwner().getAddress().getStreet(); // This is a violation
car.getOwnerAddressStreet(); // This is a proposed "solution"
Есть несколько проблем с этим подходом. Например: как это getOwnerAddressStreet()
реализовано? Предположительно так:
public final class Car {
private final Owner owner;
public Street getOwnerAddressStreet() {
return owner.getAddressStreet();
}
...
}
Таким образом, введены два новых метода, но не было никаких реальных структурных или конструктивных изменений. Можно утверждать, что, хотя Закон был технически соблюден, дух Закона все еще нарушался.
Структура все еще видна в имени метода, и мы все знаем, что вызывающий абонент хочет получить по улице адрес владельца автомобиля.
Вопрос в том, почему звонящий хочет этого, и как он / она узнает, что эта информация существует на данный момент? Это при применении закона сочетается с объектно-ориентированным дизайном. Очевидно, что цель Закона не в том, чтобы преодолеть дополнительные препятствия на нашем пути и сделать нашу жизнь более сложной. Точно так же, как наша работа заключается не в том, чтобы убедиться, что мы технически соблюдаем все правила, не задумываясь о нашем общем дизайне.
Закон ясно говорит о том, что вы не должны заходить на улицу в этой структуре. Как, в, вы не должны даже знать, что это там. Не обманывайте определение, вводя методы-оболочки! Здесь есть более глубокая проблема дизайна, которую нужно решить (без каламбура)!
Чистые «структуры данных»
В своей книге « Чистый код» у Роберта Мартина есть короткий раздел о законе Деметры, в котором он делает два интересных замечания. Один из них заключается в том, что, возможно, используя прямой доступ к переменным, можно обойти закон. Так что вместо этого …
car.getOwner().getAddress().getStreet();
… что неприемлемо, оно может быть преобразовано в следующее, что соответствует закону, поскольку оно не включает вызовы методов:
car.owner.address.street; // Using direct access to fields
Есть два аргумента против этой линии рассуждений. Во-первых, он все еще пытается обойти Закон, а не прислушиваться к его советам. И второе: прямой доступ к полям, вероятно, все еще квалифицируется как «отправка сообщения», поскольку это все еще просто связь между двумя объектами.
В отношении вышесказанного дядя Боб высказал более нюанс, что Закон Деметры в любом случае не должен применяться к чистым структурам данных.
Чистые структуры данных («объекты», которые не имеют поведения, просто данные) являются неотъемлемыми строительными блоками процедурного, функционального или смешанного подхода к парадигме. Как таковые, они, конечно, освобождаются от необходимости соблюдать Закон Деметры, который является Законом для объектно-ориентированного развития.
Геттеры
Большинство, если не все отрицательные примеры приведенного закона Деметры связаны с методами «добытчика». Вот такие методы:
public final class Car {
private final Owner owner;
public Owner getOwner() {
return owner;
}
}
Это не совпадение, что это так. Давайте представим другой метод, который использует этот геттер:
public final class Garage {
public void isAllowed(Car car) {
Owner owner = car.getOwner(); // Allowed?
...
}
}
Разрешен ли вышеуказанный вызов? Ну, технически да. car
Объект является прямым параметром для метода, так что может вызывать методы этого объекта.
Однако давайте подумаем, что можно сделать с результатом этого вызова. Метод возвращает owner
объект, который не является ни параметром, ни объектом, к которому он Garage
имеет прямой доступ. Поэтому дальнейшие вызовы методов для этого объекта не могут быть. Нет toString()
, нет hashCode()
, ничего!
Это можно считать неожиданным. Таким образом, это означает, что хотя сам вызов метода получения может быть технически законным, мы не можем на самом деле использовать результат. Похоже, методы геттеров нарушают закон Деметры почти по замыслу.
Как было сказано ранее, это не случайно и не является непреднамеренным. В объектно-ориентированной парадигме объекты должны указывать другим объектам, что делать — делегировать, а не запрашивать данные у других и делать все самостоятельно.
Когда подать заявку
В сообщениях в блогах и других статьях есть мнения, в которых говорится, что Закон Деметры — это не «закон», а только «предложение» или «руководство». Или, что это хорошо в теории, но не применяется к определенным объектам, таким как структуры данных, Java Beans, сущности, бизнес-объекты, объекты значений, объекты передачи данных или объекты для представления или сохранения.
Объектно-ориентированный разработчик должен знать, что чаще всего эти мнения исходят из различных парадигм программирования, в которых закон Деметры вполне может иметь иной вес или даже интерпретацию, чем в объектно-ориентированной.
Закон Деметры — это закон земли в объектно-ориентированном развитии. Как говорит сама оригинальная статья, это закон хорошего стиля. Техническое и духовное соблюдение Закона повсеместно — это минимум, необходимый для создания правильного, хорошо разработанного объектно-ориентированного кода.
Вывод
Закон Деметры — это очень четко определенный набор правил для объектно-ориентированного развития. Следование этим правилам буквально и в духе не требует больших усилий, если кто-либо применяет хорошие принципы объектно-ориентированного проектирования.
Кроме того, он генерирует четкие сигналы, если код отклоняется от объектно-ориентированного пути, поэтому он является бесценным инструментом для поддержания правильного направления наших проектов и стиля кода.