Дублирование видимых данных
Проблема
Данные предметной области программы хранятся в классах, отвечающих за пользовательский интерфейс (GUI).
Решение
Имеет смысл выделить данные предметной области в отдельные классы и, таким образом, обеспечить связь и синхронизацию между классом предметной области и GUI.
Причины рефакторинга
Вы хотите иметь несколько видов интерфейса для одних и тех же данных (например, у вас есть приложение не только для десктопа, но также для телефонов и планшетов). В этом случае вам будет очень сложно избежать большого количества ошибок и дублирования кода, если вы не разделите GUI и предметную область.
Достоинства
-
Вы разделяете ответственность между классами бизнес-логики и представления (принцип единственной обязанности), что упрощает читабельность и понимание программы в целом.
-
Если потребуется добавить новый вид интерфейса, вам нужно будет создать новые классы представления, при этом код бизнес-логики трогать нет никакой нужды (принцип открытости/закрытости).
-
Над бизнес-логикой и пользовательскими интерфейсами теперь могут работать разные люди.
Когда нельзя применить
-
Этот рефакторинг, который в классическом исполнении производится с введением паттерна Наблюдатель, малоприменим для веб-приложений, где все классы пересоздаются между запросами к веб-серверу.
-
Тем не менее, общий принцип извлечения бизнес-логики в отдельные классы имеет смысл, в том числе, и для веб-приложений. Но реализуется он при помощи других рефакторингов, исходя из дизайна вашей системы.
Порядок рефакторинга
-
Необходимо скрыть прямой доступ к данным предметной области в классе GUI, для чего лучше всего использовать самоинкапсуляцию поля. Таким образом, вы создадите геттеры и сеттеры к этим данным.
-
В обработчиках событий класса GUI используйте сеттеры для установки новых значений полей. Это даст возможность передавать новые значения в связанный объект предметной области.
-
Создайте класс предметной области и скопируйте в него необходимые поля из класса GUI. Для всех этих полей создайте геттеры и сеттеры.
-
Примените паттерн Наблюдатель к этим двум классам:
-
В классе предметной области создайте массив для хранения объектов наблюдателей (объектов GUI), а также методы их регистрации, удаления и оповещения.
-
В классе GUI создайте поле для хранения ссылки на объект предметной области, а также метод
update()
, который будет реагировать на изменения в этом объекте и обновлять значения полей в классе GUI. Обратите внимание, в методе обновления значения должны устанавливаться напрямую, чтобы избежать рекурсии. -
В конструкторе класса GUI создайте экземпляр класса предметной области и сохраните его в созданном поле. Зарегистрируйте объект GUI как наблюдатель в объекте предметной области.
-
В сеттерах полей класса предметной области вызывайте метод оповещения наблюдателя (т.е. метод обновления в классе GUI), чтобы передать новые значения в пользовательский интерфейс.
-
Измените сеттеры полей класса GUI так, чтобы они теперь устанавливали новые значения в объекте предметной области, причём напрямую. Будьте внимательны, если значения будут устанавливаться через сеттер класса предметной области, это приведёт к бесконечной рекурсии.
-