Статьи

Прекрасный Закон Деметры

Мы все знаем, и я надеюсь, что мы пытаемся применить «правила» объектно-ориентированного программирования при кодировании. Мы хотим, чтобы наш код был правильно инкапсулирован, слабо связан, надежен и многократно использован, а наше приложение стало более масштабируемым и обслуживаемым.

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

Но есть закон, который помогает нам принимать эти решения: закон Деметры.

Один из самых лаконичных законов объектно-ориентированного программирования, его прелесть в том, что он может применяться напрямую и гарантирует все абстрактные правила, которые я упоминал ранее.

Так что же это?

Закон звучит так: общайтесь только со своими непосредственными друзьями. Вот и все. Это действительно так просто. Так что же это значит в объектно-ориентированном программировании? Ну, «разговор» с объектом на самом деле вызывает один из его методов.

Итак, согласно Закону Деметры, метод M объекта O должен вызывать только следующие методы:

  • Методы самого Объекта O (потому что вы можете говорить сами с собой)

  • Методы объектов, переданных в качестве аргументов M

  • Методы объектов, хранящихся в переменной экземпляра

  • Методы объектов, которые создаются локально в методе М

  • Методы статических полей

Закон гласит, что вам не нужно знать внутренности объекта, который вы получаете, или любого другого объекта, который вам нужен. Вы просто обеспокоены собственной бизнес-логикой метода, и если вам нужно что-то из других объектов, которые вы говорите, не спрашивайте . Вы позволяете другим объектам отвечать за их внутреннее состояние и то, что они вам дают.

Давайте посмотрим на пример. У нас есть список сотрудников, и мы хотим получить их название улицы. Вот типичный способ сделать это.

class BasicExample {
 public String getStreetName(Employee employee) {
  Address address = employee.getAddress();
  return address.getStreetName();
 }
}

Что с ним не так и почему это не соответствует закону Деметры? Здесь у вас есть доступ к объекту сотрудника и, чтобы получить название улицы, вы получаете доступ к его полю адреса.

Зачем вам знать, что в этом случае объект Employee имеет поле Address? Вам просто нужно получить информацию от Employee, и вам все равно, как ее хранит класс Employee.

Это нарушение закона, поскольку адрес объекта не является «непосредственным» другом. У нас был доступ к нему, потому что мы получили его от объекта сотрудника.

В этом случае, мы должны изменить код, как это, чтобы уважать закон.

class Employee {
 private Address address;

 public String getStreetName() {
  return address.getStreetName();
 }
}

class BasicExample {
 public String getStreetName(Employee emplyee) {
  employee.getStreetName();
 }
}

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

Цепные Звонки

Что насчет них? Они разрешены? По-разному. Допустим, у нас есть такой код:

o.getA().getB().getC();

Если эти методы являются геттерами, которые возвращают разные поля, то это не соответствует закону. Если метод непосредственного друга возвращает другой объект — например, одно из его полей — тогда вам не следует «разговаривать» с этим возвращенным объектом. o.getA () является приемлемым вызовом, но этот метод получения вернет одно из полей объекта o, поэтому закон запрещает будущие вызовы.

Но когда разрешены цепные вызовы? Ну, закон определяет, что вы можете вызывать методы объектов, которые создаются локально в методе М. Так что, если у нас есть метод, который возвращает новый объект, как этот:

class C {
 public M createM() {
  return new M();
 }
}

тогда этот звонок

 c.createM().getO()

уважает закон. Новый объект M, который мы получаем при вызове метода createM (), является новым объектом только для нас. Этот объект не будет существовать где-либо еще. Он живет только в текущей области видимости блока, и мы можем без проблем вызывать его методы. Можно сказать, что этот аспект закона обеспечивает безопасность, потому что, если новый объект не упоминается в других местах, то вызов его методов будет иметь только локальное влияние.

Итак, мы увидели, что закон не имеет ничего против цепных вызовов.

Закон и Образ Строителя

public static class PizzaBuilder {

 private Integer size;
 private String topping;

 public Pizza build() {
  Pizza pizza = new Pizza();
  pizza.size = this.size;
  pizza.topping = this.topping;
  return pizza;
 }

 public PizzaBuilder setSize(Integer size) {
  this.size = size;
  return this;
 }

 public PizzaBuilder setTopping(String topping) {
  this.topping = topping;
  return this;
 }
}

public class Test {
 public Pizza createPizza() {
  PizzaBuilder pizzaBuilder = new PizzaBuilder();
  Pizza pizza = pizzaBuilder.setSize(30).setTopping(“Cheese”).build();

  return pizza;
 }
}

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

pizzaBuilder.setSize(30) //-> Pizza builder is created locally so it is acceptable.
  //And the set(Size) method returns “this” which is the pizzaBuilder created before.

pizzaBuilder.setSize(30).setTopping(“Cheese”) // ->  Here we call setTopping on
  //the pizzaBuilder returned by setSize, which is the pizzaBuilder
  //object instantiated before. Acceptable again.

pizzaBuilder.setSize(30).setTopping(“Cheese”).build(); // ->And finally we get our
  //pizza. And it is acceptable because setTopping, as setSize, returns the
  //pizzaBuilder and we can call any of its methods.

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

Исключения (которые подтверждают правило)

Допустим, мы получаем структуру данных в качестве параметра. Мы можем вызывать его методы, и мы можем повторять его, соблюдая закон, очевидно. Но как насчет доступа к элементам списка? Это приемлемо?

Давайте посмотрим на метод, который повторяет список старомодным способом:

public void m(List < Order > orders) {
 for (int i = 0; i < orders.size(); i++) { //Acceptable
  Order order = orders.get(i); // Acceptabe
  order.buy(); // Accetable?
 }
}

Очевидно, что orders.size () и orders.get (i) — это вызовы, которые соответствуют закону Деметры. С помощью orders.get (i) мы получаем объект order, который в данном случае не является объектом, созданным в этом вызове, поэтому, строго говоря, order.buy () не соблюдает закон. Но объект «заказы» — это список, набор объектов, по определению, который мы получаем в качестве параметра. Вот и вся цель коллекций. Таким образом, мы могли бы сказать, что в этом методе мы получаем не один объект, а все элементы списка в качестве наших параметров. Элементы списка, которые мы можем сказать, являются непосредственными друзьями, и приемлемо вызывать их методы.

Мы могли бы использовать некоторые декораторы, чтобы обернуть список в другой объект и получить доступ к элементам другим способом, но это не обязательно.

Резюме

Закон не универсален. Он не предоставляет всего, что нам нужно в объектно-ориентированном программировании. Но мы ясно получаем огромные преимущества, если мы уважаем это. У нас будет понятный, более гибкий и более понятный код.

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