MVC pattern in Delphi (Model-view-controller , "Модель-представление-поведение", "Модель-представление-контроллер").
Как известно, реализаций одной идеи может быть несколько, а на практике желательно выбрать оптимальную для конкретного случая. Однако хороших реализаций хороших идей не так много и думаю, имеет смысл их обсуждать :) Хочется рассказать и обсудить эволюцию реализации шаблона проектирования MVC, которая происходила в моих проектах на Delphi.
Для начала, что бы определится с терминами, процитирую описание шаблона:
Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента
Первая реализация MVC, которую я увидел, придя в довольно большой проект, выглядела классически - на каждый элемент шаблона была своя иерархия самописных классов, которые стыковались между собой в соответствии с логикой шаблона и приложения. Все хорошо работало, разделение кода было на высоте, но был момент, который несколько огорчал - в контроллерах, работавших с базой данных все датасэты и привязки к данным нужно было создавать/прописывать руками. Если вспомнить, что Delphi - это RAD средство, призванное максимально уменьшить количество типового кода, то этот факт огорчал вдвойне ;) Для приложений, не работающих с БД, я продолжал использовать такой вариант реализации абсолютно без проблем. Но пару лет назад, после 3-х летнего перерыва опять пришлось писать приложение, работающее преимущественно с базой. Пришлось задумался, как не потерять возможность работы в редаторе для настройки интерфейса и биндингов, при использовании шаблонов. Из нескольких вариантов у меня победила реализация MVC c использованием фреймов (TFrame).
Итак, описание моей реализации MVC для Delphi:
Иерархия классов будет выглядеть так:
-> TFraDepartmentsBase
TFraEmployeeBase(Controller) -> TFraEmployee(View, работа с 1-й записью, например на каждое поле - свой Edit)
-> TFraEmployeeTbl(View, работа в табличном виде)
TFraDepartmentsBase(Controller) -> TFraDepartments(View, работа с 1-й записью)
-> TFraDepartmentsTbl(View, работа в табличном виде)
ну и 2-й вид View, собственно окна, которые видит пользователь
TForm -> TFrmBase -> TFrmEmployeeList(допустим, просто список служащих - тогда содержит TFraEmployeeTbl)
-> TFrmDepartmentsList(если просто список отделов- тогда содержит TFraDepartmentsTbl)
-> TFrmEmployeeByDepartments(список служащих по отделам - TFraDepartmentsTbl и TFraEmployeeTbl, связанные как мастер-детали)
Теперь попробую объяснить, зачем это было городить ;)
Не раз приходилось видеть, как люди с "альтернативным мышлением" ;) в проектах рисовали иерархию классов, основываясь на визуальном поведении, а не бизнес логике. Этот кошмар часто выглядел примерно так:
1-но табличный Справочник -> 2-х табличный Справочник ->...
Потом к этому ужасу пишут изощренные инициализаторы, что бы это работало с разной бизнес логикой, а когда не получается - ветвят формы и через полгодна мало кто до конца помнит, почему восемь 2-х табличных справочников и пять 3-х табличных ;)
А теперь сравним, что произойдет, когда нужно будет добавить, 1 поле к таблице сотрудников из описанного выше примера (сотрудники и отделы). У альтернативщиков это целая РАБОТА - надо же не поломать соседнюю бизнес-логику ;) Унас - поправить TFraEmployeeBase(код), если нужно отображение добавить - то поправить TFraEmployee и TFraEmployeeTbl(мышкой ;) и все - сколько бы ни было форм, где идет работа с сотрудниками, изменения произойдут везде.
Сразу хочется ответить на типичные вопросы, которые не раз слышал от всяких неучей ;) :
Ну и конце, таки попробую найти недостатки ;)
Как известно, реализаций одной идеи может быть несколько, а на практике желательно выбрать оптимальную для конкретного случая. Однако хороших реализаций хороших идей не так много и думаю, имеет смысл их обсуждать :) Хочется рассказать и обсудить эволюцию реализации шаблона проектирования MVC, которая происходила в моих проектах на Delphi.
Для начала, что бы определится с терминами, процитирую описание шаблона:
Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента
- Модель (Model). Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контроллера), изменяя своё состояние.
- Представление (View). Отвечает за отображение информации (пользовательский интерфейс).
- Поведение (Controller). Интерпретирует данные, введённые пользователем, и информирует модель и представление о необходимости соответствующей реакции.
Первая реализация MVC, которую я увидел, придя в довольно большой проект, выглядела классически - на каждый элемент шаблона была своя иерархия самописных классов, которые стыковались между собой в соответствии с логикой шаблона и приложения. Все хорошо работало, разделение кода было на высоте, но был момент, который несколько огорчал - в контроллерах, работавших с базой данных все датасэты и привязки к данным нужно было создавать/прописывать руками. Если вспомнить, что Delphi - это RAD средство, призванное максимально уменьшить количество типового кода, то этот факт огорчал вдвойне ;) Для приложений, не работающих с БД, я продолжал использовать такой вариант реализации абсолютно без проблем. Но пару лет назад, после 3-х летнего перерыва опять пришлось писать приложение, работающее преимущественно с базой. Пришлось задумался, как не потерять возможность работы в редаторе для настройки интерфейса и биндингов, при использовании шаблонов. Из нескольких вариантов у меня победила реализация MVC c использованием фреймов (TFrame).
Итак, описание моей реализации MVC для Delphi:
- Модель (Model) - тут ничего не меняется - это либо данные из БД, либо какой-то класс.
- Поведение (Controller) - базовый контроллер - наследник от TFrame.
- Представление (View) - базовое представление - наследник от фрэйма-Controller'а.
Иерархия классов будет выглядеть так:
- Контроллеры
-> TFraDepartmentsBase
- Представление - вот тут появляется нюанс, из-за которого я все это и пишу ;) а то знаете ли объяснять одно и то же много раз таки накладно ;) Поскольку было большое желание оставить все плюшки работы в IDE появился такой нюанс - некоторые представления(View) - наследники описанных контроллеров, НО в них - ни единой строки бизнес-логики, только код, отвечающий за GUI:
TFraEmployeeBase(Controller) -> TFraEmployee(View, работа с 1-й записью, например на каждое поле - свой Edit)
-> TFraEmployeeTbl(View, работа в табличном виде)
TFraDepartmentsBase(Controller) -> TFraDepartments(View, работа с 1-й записью)
-> TFraDepartmentsTbl(View, работа в табличном виде)
ну и 2-й вид View, собственно окна, которые видит пользователь
TForm -> TFrmBase -> TFrmEmployeeList(допустим, просто список служащих - тогда содержит TFraEmployeeTbl)
-> TFrmDepartmentsList(если просто список отделов- тогда содержит TFraDepartmentsTbl)
-> TFrmEmployeeByDepartments(список служащих по отделам - TFraDepartmentsTbl и TFraEmployeeTbl, связанные как мастер-детали)
Теперь попробую объяснить, зачем это было городить ;)
- Поскольку контроллер - фрэйм - все датабиндинги настраиваются в IDE, что не сравнить с ручным созданием всей этой обвязки.
- Поскольку конкретное отображение конкретного куска биснес-логики лежит на фрейме-наследнике, то привязки к гридам(Grid) и эдитам(Edit) делается в IDE так, что многие завидуют - МЫШКОЙ ;)
- С учетом п.1 и 2 на конечном представление - на форме лежат фреймы, между которыми можно делать, например связи мастер-детали прямо из IDE. В итоге в модуле формы только код по инициализации фремов, возможно еще несколько строк по взимодействию между фреймами, но ВООБЩЕ НЕТ бизенс-логики - она вся во фреймах *Base.
- Такая реализация дает возможность очень быстро менять вид конечной формы - накидали нужных фреймов, связали между собой - все новый вариант отображения ;) В общем, форму выкинуть не жалко - кода там почти нет :)
- В умных книгах регулярно пишут, что злоупотреблений наследованием - это недоработка в вашей архитектуре. При описанной схеме максимальный уровень наследования контроллеров 4-5, обычный 2-3 и не растет даже призначительных переделках. Про отображение можно сказать тоже самое.
Не раз приходилось видеть, как люди с "альтернативным мышлением" ;) в проектах рисовали иерархию классов, основываясь на визуальном поведении, а не бизнес логике. Этот кошмар часто выглядел примерно так:
1-но табличный Справочник -> 2-х табличный Справочник ->...
Потом к этому ужасу пишут изощренные инициализаторы, что бы это работало с разной бизнес логикой, а когда не получается - ветвят формы и через полгодна мало кто до конца помнит, почему восемь 2-х табличных справочников и пять 3-х табличных ;)
А теперь сравним, что произойдет, когда нужно будет добавить, 1 поле к таблице сотрудников из описанного выше примера (сотрудники и отделы). У альтернативщиков это целая РАБОТА - надо же не поломать соседнюю бизнес-логику ;) Унас - поправить TFraEmployeeBase(код), если нужно отображение добавить - то поправить TFraEmployee и TFraEmployeeTbl(мышкой ;) и все - сколько бы ни было форм, где идет работа с сотрудниками, изменения произойдут везде.
Сразу хочется ответить на типичные вопросы, которые не раз слышал от всяких неучей ;) :
- Delphi живее всех живых.
- Фрэймы не зло - вы просто не умеете их готовить ;)
- Все получается быстро и удобно.
Ну и конце, таки попробую найти недостатки ;)
- Ну, на начальном этапе, пока вся обвязка только пишется, первые пол дня вы не сможете показать форму с кнопкой "Счастье" (хотя, когда обвязка готова, то в рамках такой реализации, "Счастье" будет появляться очень быстро ;)
- Единственный реальный недостаток - на формах в фреймами иногда подглючивает редатор и в dfm может попасть мусор, вплоть до того, что форма перестанет открыватся в IDE. Но ведь все пользуются хорошей системой контроля версий, делают регулярные коммиты и умеют читать/править dfm без IDE, правда? :)
Немає коментарів:
Дописати коментар