Autumn SALE

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

Также известен как: Duplicate Observed Data

Проблема

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

Решение

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

До
Duplicate Observed Data - Before
После
Duplicate Observed Data - After

Причины рефакторинга

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

Достоинства

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

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

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

Когда нельзя применить

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

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

Порядок рефакторинга

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

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

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

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

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

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

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

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

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