Логотип ConstructiCat
CodeBust.
Навигация по разделу ▾

Дублирование видимых данных.

Проблема

Данные предметной области программы хранятся в классах, отвечающих за пользовательский интерфейс (GUI).

Решение

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

##Пример

До
class IntervalWindow {
  String startText = "0";
  String endText = "0";
  String lengthText = "0";

  void onStartChanged(String text) {
    startText = text;
    calculateLength();
  }

  void onEndChanged(String text) {
    endText = text;
    calculateLength();
  }

  void calculateLength() {
    int length = Integer.parseInt(endText) - Integer.parseInt(startText);
    lengthText = String.valueOf(length);
  }
}
После
interface IntervalObserver {
  void update(Interval subject);
}

class IntervalWindow implements IntervalObserver {
  private final Interval subject = new Interval();
  String startText;
  String endText;
  String lengthText;

  IntervalWindow() {
    subject.addObserver(this);
    update(subject);
  }

  void onStartChanged(String text) {
    subject.setStart(Integer.parseInt(text));
  }

  void onEndChanged(String text) {
    subject.setEnd(Integer.parseInt(text));
  }

  public void update(Interval subject) {
    startText = String.valueOf(subject.getStart());
    endText = String.valueOf(subject.getEnd());
    lengthText = String.valueOf(subject.getLength());
  }
}

class Interval {
  private final List<IntervalObserver> observers = new ArrayList<>();
  private int start;
  private int end;

  void addObserver(IntervalObserver observer) { observers.add(observer); }
  void removeObserver(IntervalObserver observer) { observers.remove(observer); }

  int getStart() { return start; }
  int getEnd() { return end; }
  int getLength() { return end - start; }

  void setStart(int arg) {
    start = arg;
    notifyObservers();
  }
  void setEnd(int arg) {
    end = arg;
    notifyObservers();
  }

  private void notifyObservers() {
    for (IntervalObserver observer : observers) {
      observer.update(this);
    }
  }
}

##Зачем делать рефакторинг

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

##Преимущества

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

  • Если потребуется добавить новый вид интерфейса, вам нужно будет создать новые классы представления, при этом код бизнес-логики трогать нет никакой нужды (принцип открытости/закрытости).

  • Над бизнес-логикой и пользовательскими интерфейсами теперь могут работать разные люди.

##Как делать рефакторинг

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

  2. В обработчиках событий класса GUI используйте сеттеры для установки новых значений полей. Это даст возможность передавать новые значения в связанный объект предметной области.

  3. Создайте класс предметной области и скопируйте в него необходимые поля из класса GUI. Для всех этих полей создайте геттеры и сеттеры.

  4. Примените паттерн Наблюдатель к этим двум классам:

    • В классе предметной области создайте массив для хранения объектов наблюдателей (объектов GUI), а также методы их регистрации, удаления и оповещения.

    • В классе GUI создайте поле для хранения ссылки на объект предметной области, а также метод update(), который будет реагировать на изменения в этом объекте и обновлять значения полей в классе GUI. Обратите внимание, в методе обновления значения должны устанавливаться напрямую, чтобы избежать рекурсии.

    • В конструкторе класса GUI создайте экземпляр класса предметной области и сохраните его в созданном поле. Зарегистрируйте объект GUI как наблюдатель в объекте предметной области.

    • В сеттерах полей класса предметной области вызывайте метод оповещения наблюдателя (т.е. метод обновления в классе GUI), чтобы передать новые значения в пользовательский интерфейс.

    • Измените сеттеры полей класса GUI так, чтобы они теперь устанавливали новые значения в объекте предметной области, причём напрямую. Будьте внимательны, если значения будут устанавливаться через сеттер класса предметной области, это приведёт к бесконечной рекурсии.