Подтвердить что ты не робот

Как действуют Закон Деметры и состав с коллекциями?

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

Скажем, у вас есть довольно стандартный объект, состоящий из других объектов, например:

public class Customer {
  private String name;
  private ContactInfo primaryAddress;
  private ContactInfo workAddress;
  private Interests hobbies;
  //Etc...

  public getPrimaryAddress() { return primaryAddress; }
  public getWorkAddress() { return workAddress; }
  public getHobbies() { return hobbies; }
  //Etc...
}

private ContactInfo {
  private String phoneNumber;
  private String emailAddress;
  //Etc...

  public getPhoneNumber() { return phoneNumber; }
  public getEmailAddress() { return emailAddress; }
  //Etc...
}

private Interests {
  private List listOfInterests;
}

Следующие будут нарушать Закон Деметры:

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber());
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString());

Это также нарушит Закон Деметры, я думаю (разъяснение?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress();
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber());

Итак, предположим, вы добавили бы "getPrimaryPhoneNumber()" к клиенту:

public getPrimaryPhoneNumber() {
  return primaryAddress.getPhoneNumber();
}

А потом просто позвоните:   System.out.println( "Телефон:" + customer.getPrimaryPhoneNumber());

Но делать это со временем кажется, что это фактически обеспечит много проблем и будет работать против намерения Закона Деметры. Это делает класс Клиента огромным пакетом геттеров и сеттеров, который имеет слишком много знаний о своих внутренних классах. Например, представляется возможным, что объект Customer однажды будет иметь разные адреса (а не только "первичный" и "рабочий" адрес). Возможно, даже класс Customer просто будет иметь список (или другую коллекцию) объектов ContactInfo, а не конкретные объекты ContactInfo. Как вы продолжаете следовать Закону Деметры в этом случае? Казалось бы, это победит цель абстракции. Например, это кажется разумным в таком случае, когда у Клиента есть список элементов ContactInfo:

Customer.getSomeParticularAddress(addressType).getPhoneNumber();

Кажется, что он может стать еще более сумасшедшим, когда вы думаете о некоторых людей, имеющих мобильный телефон и стационарный телефон, а затем ContactInfo должен иметь набор телефонных номеров.

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber();

В этом случае мы не только ссылаемся на объекты внутри объектов внутри объектов, но также должны знать, каков действительный тип addressType и phoneType. Я определенно вижу проблему с этим, но я не уверен, как этого избежать. Особенно, когда какой-либо класс вызывает это, вероятно, знает, что они хотят вытащить "мобильный" номер телефона для "первичного" адреса данного клиента.

Как это можно было бы реорганизовать, чтобы соответствовать Закону Деметры и почему это было бы хорошо?

4b9b3361

Ответ 1

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

В общем, модификатор частного доступа увеличивает скрытие информации, что является основой Закона Деметры. Выявление частных классов противоречиво. Средство IDE NetBeans на самом деле включает предупреждение компилятора по умолчанию для "Экспорт непубличного типа через открытый API".

Я утверждал бы, что разоблачение частного класса за пределами его охватывающего класса всегда плохое: он уменьшает информацию, скрывая и нарушая Закон Деметры. Поэтому, чтобы ответить на вопрос о разъяснении вопроса о возврате экземпляра ContactInfo вне Customer: да, это нарушение.

Предлагаемое решение о добавлении метода getPrimaryPhoneNumber() к Customer является допустимым вариантом. Путаница здесь: "Клиент... имеет слишком много знаний о своих собственных внутренних классах". Это невозможно; и поэтому важно, чтобы этот пример не был стандартным составом.

Вложенный класс имеет 100% знание любых вложенных классов. Всегда. Независимо от того, как эти вложенные классы используются в охватывающем классе (или где-либо еще). Вот почему закрытый класс имеет прямой доступ к закрытым полям и методам своих вложенных классов: класс-оболочка по сути знает все о них, потому что theyre реализован внутри него.

Учитывая нелепый пример класса Foo, который имеет вложенный класс Bar, который имеет вложенный класс Baz, который имеет вложенный класс Qux, это не было бы нарушением Demeter для Foo (внутренне) для вызова bar. baz.qux.method(). Фу уже знает все, что нужно знать о Бар, Баз и Квакс; потому что их код находится внутри Foo, поэтому нет дополнительных знаний, передаваемых через длинную цепочку методов.

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

Например, последний фрагмент кода может быть реорганизован как, customer.getPhoneNumber(addressType, phoneType);

или если имеется только небольшое количество опций, customer.getPrimaryMobilePhoneNumber();

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

Ответ 2

Как создать другой класс с именем типа CustomerInformationProvider. Вы можете передать свой объект Customer в качестве параметра конструктора в свой новый класс. И тогда вы можете написать все специальные методы для получения телефонов, адресов и т.д. Внутри этого класса, тем самым оставив класс Customer чистым.

Ответ 3

Важно помнить, что Закон Деметры , несмотря на его название, рекомендация, а не фактический закон. Нам нужно изучить его цель на чуть более глубоком уровне, чтобы определить, что именно нужно делать здесь.

Цель Закона Деметры - предотвращать доступ внешних объектов к внутренним объектам другого объекта. Доступ к внутренним компонентам имеет две проблемы: 1) он дает слишком много информации о внутренней структуре объекта и 2) он также позволяет внешним объектам изменять внутренние элементы класса.

Правильный ответ на эту проблему - выделить объект, возвращаемый из метода Customer из внутреннего представления. Другими словами, вместо того, чтобы возвращать ссылку на частный внутренний объект типа ContactInfo, мы вместо этого определяем новый класс UnmodifiableContactInfo, а получаем getPrimaryAddress, возвращаем UnmodifiableContactInfo, создавая его и заполняя при необходимости.

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

(В действительности я бы переименовал внутренний класс и оставил внешний как ContactInfo, но это небольшая точка)

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

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

Несколько заметок. Очевидно, что чрезмерное применение закона Деметра приводит к идиотизму, запрещающему, например:

int nameLength = myObject.getName().length()

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

mylist.get(0).doSomething();

что является технически нарушением. Но реальность такова, что ни одна из этих проблем не является проблемой, если мы фактически не позволяем внешнему коду влиять на поведение основного объекта (Клиента) на основе найденных объектов.

Резюме

Вот как выглядит ваш код:

public class Customer {
    private class InternalContactInfo {
        public ContactInfo createContactinfo() {
            //creates a ContactInfo based on its own values...
        }
        //Etc....
    }
   private String name;
   private InternalContactInfo primaryAddress;
   //Etc...

   public Contactinfo getPrimaryAddress() { 
      // copies the info from InternalContactInfo to a new object
      return primaryAddress.createContactInfo();
   }
   //Etc...
}

public class ContactInfo {
   // Not modifiable
   private String phoneNumber;
   private String emailAddress;
   //Etc...

   public getPhoneNumber() { return phoneNumber; }
   public getEmailAddress() { return emailAddress; }
   //Etc...
}

}