Когда геттеры и сеттеры оправданы?

Когда геттеры и сеттеры оправданы?
Когда геттеры и сеттеры оправданы? - akewidyastomo @ Unsplash

Наличие геттеров и сеттеров само по себе не нарушает инкапсуляцию

Что нарушает инкапсуляцию, так это автоматическое добавление геттера и сеттера для каждого члена данных (каждого поля на жаргоне java), не задумываясь об этом. Хотя это лучше, чем публиковать все элементы данных, это всего лишь небольшой шаг.

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

  • Некоторые члены данных могут быть полностью внутренними по отношению к объекту и должны не имеют ни геттеров, ни сеттеров.

  • Некоторые члены данных должны быть доступны только для чтения, поэтому им могут понадобиться геттеры, но не сеттеры.

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

  • Некоторые члены данных могут быть изменены только определенным образом, например увеличивается или уменьшается на фиксированную величину. В этом случае вы предоставит метод increment() и/или decrement(), а не чем сеттер.

  • Тем не менее, другим может действительно потребоваться чтение-запись, и у них будет как геттер, так и геттер. и сеттер.

Рассмотрим пример class Person. Допустим, у человека есть имя, номер социального страхования и возраст. Допустим, мы не позволяем людям когда-либо менять свои имена или номера социального страхования. Однако возраст человека следует увеличивать на 1 каждый год. В этом случае вы должны предоставить конструктор, который будет инициализировать имя и SSN заданными значениями, а также инициализировать возраст до 0. Вы также должны предоставить метод incrementAge(), который увеличит возраст на 1. Вы также предоставить геттеры для всех трех. Никаких сеттеров в этом случае не требуется.

В этом дизайне вы позволяете проверять состояние объекта извне класса и разрешаете изменять его извне класса. Однако вы не позволяете произвольно изменять состояние. Существует политика, которая фактически гласит, что имя и SSN вообще нельзя изменять, а возраст можно увеличивать на 1 год за раз.

Теперь допустим у человека тоже есть зарплата. И люди могут менять работу по своему желанию, а значит, изменится и их зарплата. Чтобы смоделировать эту ситуацию, у нас нет другого пути, кроме как предоставить метод setSalary()! Позволить себе изменять зарплату по своему усмотрению — вполне разумная политика в данном случае.

Кстати, в вашем примере я бы дал классу Fridge методы putCheese() и takeCheese() вместо get_cheese() и set_cheese(). Тогда у вас все равно будет инкапсуляция.


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if ( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if ( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if ( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if ( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if ( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}

Рекомендую посмотреть эти видео для лучшего погружения в вопрос:

Прикрепленное видео 1 - Геттеры и сеттеры в javascript. get и set в js. Урок 37

Прикрепленное видео 2 - Что такое геттеры и сеттеры для класса. Методы get и set. Инкапсуляция это. Пример. C++ Урок - 76


LetsCodeIt, 26 мая 2023 г., 21:24