Статьи

Закон Деметры и как с ним работать

Закон Деметры — интересный принцип программирования. Это единственный, который я знаю, который имеет почти математическое определение:

Любой метод m объекта O может вызывать только методы следующих типов объектов:

  • О себе
  • параметры м
  • Любые объекты, созданные / созданные в течение м
  • Прямые составляющие объекты O
  • Глобальная переменная, доступная O , в области видимости m

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

Почему?

Какова цель закона Деметры? Что он пытается предотвратить? Закон Деметры предназначен для уменьшения сцепления и увеличения инкапсуляции. Следуя закону Деметры, вы делаете так, чтобы классу O не нужно было ничего знать об источнике m, кроме того, что делает m . O не должен знать ничего о том, что возвращает m , кроме его существования, чтобы уменьшить связь с ним.

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

Anti-Pattern

Начнем с анти-паттерна. Я назвал это «Слоистые частные методы», и это была моя первая мысль о том, чтобы «обойти» Закон Деметры, но я быстро понял, что, хотя он технически следует приведенным правилам, он не следует причине закона.

Анти-шаблон работает так. Скажем, у вас изначально был следующий фрагмент кода, который вы хотите изменить, чтобы он соответствовал Demeter:

1
2
3
4
5
public void m(Parameter parameter)
{
   ReturnValue result = parameter.method();
   result.method();
}

Чтобы исправить это, вы измените это на это:

1
2
3
4
5
6
7
8
9
public void m(Parameter parameter)
{
   otherMethod(parameter.method());
}
 
private void otherMethod(ReturnValue rv)
{
   rv.method();
}

Как видите, каждый метод все еще следует закону, но класс все еще связан. Всякий раз, когда вы вызываете другой метод O изнутри, вы должны рассматривать код в этом вызываемом методе как часть m , чтобы все еще следовать духу Закона Деметры.

Слои

Итак, что вы должны сделать, чтобы исправить код? В этом случае вы можете превратить вызов закрытого метода в вызов нового класса:

1
2
3
4
5
6
7
public class NewClass
{
   public void extractedMethod(ReturnValue rv)
   {
      rv.method();
   }
}

Теперь я могу выглядеть так:

1
2
3
4
5
public void m(Parameter parameter)
{
   NewClass helper = new NewClass();
   helper.extractedMethod(parameter.method());
}

Теперь это может показаться полной тратой, создав новый класс, который по сути является заменой для частного метода. Но на самом деле многие считают, что каждый частный метод должен стать новым классом. Хотя я согласен с тем, что наши частные методы демонстрируют тенденцию к выходу за рамки классов, в которых они содержатся. Всякий раз, когда вы видите приватный метод, рассмотрите возможность извлечения из него класса.

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

Исключение 1: структуры данных

Когда m является методом в структуре данных или возвращает структуру данных, тогда закон Деметры можно игнорировать. Структура данных — это класс, все цели которого основаны на хранении / представлении данных. Это включает в себя обертки примитивного типа, String, «Java-бины» и другие. Понятно, что если меняется определение данных, пользователи данных должны адаптироваться к этим изменениям. Если вызываемый вами метод (который нарушает закон Деметры) просто возвращает общедоступные внутренние данные (сами данные могут быть частными, но с общедоступными получателями), нет веских причин, почему бы не разрешить это.

Неизменяемые объекты также могут быть частью нарушения закона, руководствуясь следующими рекомендациями: «плохой» вызов метода 1) возвращает открытые внутренние данные (как раньше) или 2) возвращает новый объект того же типа, что и он сам.

Номер 2 безопасен, потому что вызов метода эквивалентен увеличению числа. Если бы у целых чисел не было возможности использовать операторы, приращение было бы точно таким же, как число 2 ( n = n.increment() ), предполагая неизменные объекты.

Исключение 2. Шаблон Builder и другие свободно распространяемые API

Когда API спроектирован так, чтобы быть беглым (и, следовательно, обычно цепным), нет веских оснований терять читабельность, просто чтобы быть строгим последователем Закона Деметры. Например, Stream API java 8 был бы бесполезен, если бы вы не позволили себе связывать методы.

Outro

Я надеюсь, что теперь у вас есть лучшее понимание цели и использования Закона Деметры. А теперь иди и программируй! И веселиться!