Предварительная вводная: Говоря о классах "реализации" - будем придерживаться нотации Delphi и будем иметь в виду архитектуру библиотеки VCL. Это необязательно. Но это - для определённости.
Пусть есть несколько "похожих" прецедентов (http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D1%86%D0%B5%D0%B4%D0%B5%D0%BD%D1%82_(UML)):
1. Работа с документом
2. Работа со словарной статьёй
3. Работа со статьёй энциклопедии
4. Обзор изменений документа
5. Сравнение соседних редакций документа
Прецеденты взяты из реальной системы. Надеюсь - этому никто не расстроится. Просто качественно можно говорить об известном предмете, а не о "коне вакууме".
Надеюсь, что с одной стороны - никто не узрит в этом "выдачу технологических секретов", а с другой - "скрытую рекламу". Если бы не эти два обстоятельства - писать было бы сильно проще.
Этап сбора и описания требований - опускаю. Этой теме можно посвятить отдельную статью (если интересно). Пока предположим, что требования собраны и описаны. И частично - детализированы.
Прецеденты - РАЗНЫЕ, но по функциональности - ПЕРЕСЕКАЮТСЯ.
Что у них общего? Это - "работа с документом" в том или ином виде.
Понятное дело, что в реализации этих прецедентов - есть много общего, но и есть различия.
Понятное дело, все эти прецеденты визуализуются РАЗЛИЧНЫМИ формами отображения.
Понятно, что эти формы - родственны, но при этом - они и различаются.
Что делать? Как будем проектировать функционал этих прецедентов?
Наследование? Визуальное наследование? Агрегация? Выделение бизнес-логики в отдельный слой/слои?
Хочется немножко рассказать на эту тему.
Для начала надо наверное выделить ОБЩИЕ ответственности для данных прецедентов:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.
пока - ограничимся...
Теперь попытаемся выделить функциональные различия данных прецедентов.
Для начала поймём - как эти прецеденты - визуализируются.
На самом деле каждый прецедент (отображаемый для определённости "формой" - наследником от TForm) распадается на несколько "зон активностей" (или "зон пользовательского фокуса"). На самом деле эти "зоны" (забегая вперёд) это отдельные вложенные прецеденты. На эту тему я немножко "закинул удочку" тут - http://18delphi.blogspot.com/2013/08/usecase.html.
Каждая из зон характеризуется тем, что у них у каждой может быть СОБСТВЕННЫЙ набор Toolbar'ов, каждая может принимать ОТДЕЛЬНО фокус ввода, каждая имеет СОБСТВЕННОЕ контекстное меню, каждая может ВЛИЯТЬ на главное (командное) меню приложения.
Каждая из зон может обладать СОБСТВЕННЫМ состоянием, которое может записываться в историю работы приложения.
Предположим, что распадаются они так (в скобках буду указывать имена "реализующих классов"):
1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).
2. Работа со словарной статьёй (TDictionEntryForm):
а. Русский текст статьи (TRussianTextViewArea).
б. Английский текст статьи (TEnglishTextViewArea).
в. Список словарных статей (TDictionEntriesViewArea).
3. Работа со статьёй энциклопедии (TEncyclopediaForm):
а. Собственно "текст статьи" (TArcticleViewArea).
б. Оглавление энциклопедии (TEncyclopediaContentsViewArea).
4. Обзор изменений документа (TDocumentReviewForm):
а. Собственно "текст обзора документа" (TDocumentReviewTextViewArea).
5. Сравнение соседних редакций документа (TEditionsCompareForm):
а. Текст предыдущей редакции документа (TPrevEditionTextViewArea).
б. Текст "актуальной" редакции документа (TActualEditionTextViewArea).
Что это за классы TXXXViewArea и каково их место в иерархии того же VCL - мы поговорим - чуть позже.
Если вы хотите аналогию из "классического" Delphi и VCL, то скажем так, что TXXXViewArea - "похожи" на TFrame. Именно - ПОХОЖИ. Но НЕ ЯВЛЯЮТСЯ таковыми. Но ПОКА - так думать будет понятно (надеюсь).
Далее надо отметить, что каждый прецедент имеет "точку входа". Это нечто, что принимает "данные для конструирования прецедента" и возвращает собственно ЭКЗЕМПЛЯР прецедента.
В терминах "классики" это - конструктор класса (формы). Но если забегать вперёд и думать об "инъекции зависимостей" (http://ru.wikipedia.org/wiki/Dependency_Injection), то это - фабрика. Фабрика прецедента.
Но! ПОКА забудем о фабриках. До поры до времени.
ПОКА будем считать, что "точка входа" в прецедент это конструктор объекта в терминах "классики".
Также ПОКА забудем о сигнатуре конструктора TForm (и TComponent) и что на самом деле он выглядит так - Create(anOwner : TComponent).
Также ПОКА забудем о "геометрии" и размещении форм (визуализующих прецеденты) в пространстве, а также о свойстве Parent.
Об этом мы поговорим чуть позже. Обещаю.
ПОКА будем считать, что "точка входа" выглядит примерно так CreateUseCase(aData : TUseCaseData).
Где TUseCaseData - некоторый базовый (в пределе - шаблонный) класс, обеспечивающий связь данных прецедента и собственно логики и визуализации прецедента.
Забегая вперёд - отметим, что это скорее всего не класс, а - интерфейс. Но на данном этапе - нам ПОКА это не важно.
Почему ИНТЕРФЕЙС? Потому, что - ПОДСЧЁТ ССЫЛОК. Пока - это - не важно. Но позже - я остановлюсь и на этом моменте.
(Для тех немногих, кто знаком с фреймворком VCM скажу, что TUseCaseData - это собственно и есть IsdsXXX).
Пока дело обстоит так. Есть "визизуализация прецедента" - TXXXForm и "данные прецедента" - TXXXUseCaseData.
TXXXForm обладает конструктором CreateUseCase, с сигнатурой - CreateUseCase(aData : TXXXUseCaseData).
Давайте теперь посмотрим - какие "данные прецедентов" нам понадобятся для визуализации описанных выше прецедентов.
Тут ПОКА - всё очень просто. Отображение тут - 1:1. Т.е. "визуализация прецедента" TXXXForm требует "данных прецедента" TXXXUseCaseData.
Хочется отметить тот факт, что в описываемой "модели" - "данные прецедента" - являются КОНСТАНТНЫМИ. Т.е. они не меняют своё состояние с момента своего рождения до момента своей смерти. Это означает, что собственно прецедент (и его визуализация) могут рассматривать эти данные как инвариант на всём времени своей жизни. Если "данные прецедента" надо поменять (например переходим из одного документа в другой), то это означает, что надо СМЕНИТЬ текущий прецедент. Создав НОВЫЙ экземпляр, подав в "точку входа" (конструктор/фабрику) новые "данные прецедента". Этот ВАЖНЫЙ факт - мы тоже - обсудим. Чуть позже.
Тут бы уже можно было бы нарисовать модель прецедентов и их данных в терминах UML, но к сожалению - инструмент у меня недоступен - посему "буду писать текстом". Пока. А модель - нарисую чуть позже.
Итак - посмотрим на описанные выше прецеденты и их данные.
Пусть запись вида - TXXXForm <=| TXXXUseCaseData - означает "данные TXXXUseCaseData - могут порождать экземпляр прецедента TXXXForm". Т.е. что данные совместимы с входным параметром соответствующего прецедента.
Итак - прецеденты и их данные:
1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData
Теперь собственно рассмотрим - "что же такое эти TXXXUseCaseData":
Как мы уже "договорились" - они все наследуются от TUseCaseData.
Т.е. как-то так (ПОКА):
1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData = class(TUseCaseData)
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData = class(TUseCaseData)
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData = class(TUseCaseData)
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData = class(TUseCaseData)
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData = class(TUseCaseData)
Это - "крупными мазками".
Теперь поговорим об их наполнении.
Мы уже говорили о TXXXViewArea. "Активных зонах".
И "по индукции" было бы логично раз TXXXUseCaseData порождает TXXXForm, то должен найтись TXXXViewAreaData, который порождает TXXXViewArea.
Про "геометрию", Owner'ов и Parent'ов - опять же - ПОКА - не говорим.
Считаем, что TXXXViewArea имеет "конструктор" CreateViewArea(aData : TXXXViewAreaData).
Откуда берутся эти самые TXXXViewAreaData?
В описываемой модели - они "висят" в виде read-only-property на TXXXUseCaseData. И наследуются (забегая вперёд) от TViewAreaData.
(Для тех немногих, кто знаком с VCM скажу TXXXViewAreaData это ни что иное как IdsXXX)
И тогда картина описываемых прецедентов приобретает следующий вид:
1. Работа с документом - TDocumentForm <=|
2. Работа со словарной статьёй - TDictionEntryForm <=|
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=|
4. Обзор изменений документа - TDocumentReviewForm <=|
5. Сравнение соседних редакций документа - TEditionsCompareForm <=|
Итак - мы ПОКА имеем ЧЕТЫРЕ основных понятия - TXXXForm (прецедент), TXXXViewArea (активная зона/вложенный прецедент), TXXXUseCaseData (данные прецедента) и TXXXViewAreaData (данные активной зоны).
Теперь давайте более детально спроектируем один прецедент. Самый "большой". Первый. "Работа с документом":
Ранее мы ПОКА договорились, что TXXXViewArea это "нечто вроде TFrame". Пока этих знаний - нам достаточно.
Но кто же реально визуализирует данные и осуществляет интерактив с пользователем?
Этот момент - мы сейчас более-менее подробно - разберём.
Итак - наш прецедент "работа с документом" с точки зрения представления пользователю ПОКА выглядит вот так:
1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).
Давайте разберёмся - какие конечные компоненты визуализируют те или иные "активные зоны".
Пусть из требований следует что:
а. Собственно "текст документа" (TDocumentTextViewArea) - отображается в виде "текста с гиперссылками и прочим оформлением".
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea) - отображается в виде "древесной структуры".
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea) - отображается в виде "одноуровневого списка".
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea) - отображается в виде "одноуровневого списка".
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea) - отображается в виде "одноуровневого списка".
д. Аннотация к документу (TDocumentAnnotationViewArea)- отображается в виде "текста с гиперссылками и прочим оформлением" (как и сам текст документа).
Итак мы ПОКА имеем три вида отображения данных:
1. "текст с гиперссылками и прочим оформлением".
2. "древесная структура".
3. "список".
(Не могу не отметить, что для БОЛЬШИНСТВА бизнес-приложений этих способов представления данных - БОЛЕЕ чем достаточно)
Пусть у нас УЖЕ ЕСТЬ компоненты, которые умеет отображать структуры данных в заданном виде:
1. "текст с гиперссылками и прочим оформлением" - TDocumentView.
2. "древесная структура" - TTreeView.
3. "список" - TListView.
Тогда - будем ПОКА считать, что в каждую TXXXViewArea (которая ПОКА по договорённости является TFrame) - вставлен - соответствующий компонент. Например как alClient (это для любителе Delphi). Больше про "геометрию", Owner'а и Parent'а - опять же - ПОКА не говорим.
Теперь как выглядит описание прецедента с точки зрения представления?
А вот так:
Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:
Но теперь встаёт вопрос - "откуда же берутся данные для соответствующих компонент"? Как TXXXUseCaseData и TXXXViewAreaData коррелируют с данными для конечных компонентов?
Это вопрос - мы сейчас и разберём.
В общем - всё - по индукции.
TXXXUseCaseData - содержит в себе TXXXViewAreaData в виде read-only-property, значит - логично предположить, что TXXXViewAreaData содержит в себе TXXXViewData в виде всё тех же read-only-property.
В общем - так оно и есть.
Картина приобретает следующий вид:
(Для тех кто в курсе про VCM: TDocument это и есть TXXXDocumentContainer)
И ОПЯТЬ! Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:
Идея понятна?
Вот тут наверное подходящий момент, чтобы остановиться и спросить - "а зачем так сложно?"
Я сейчас постараюсь это пояснить.
Во-первых - мне кажется, что в общем - НИЧУТЬ не сложно. Процедура - ФОРМАЛЬНАЯ. И устроена - как "матрёшка", что само по себе (меня лично - радует).
Выделяем прецеденты, потом описываем вложенные прецеденты, потом описываем данные прецедентов и вложенных прецедентов. Потом - детализируем прецеденты и получаем компоненты, которые взаимодействуют с пользователем, потом - описываем - данные для компонентов. Процедура - ФОРМАЛЬНАЯ и рекурсивная.
А во-вторых - вся эта "сложность" во-первых направлена на взаимозаменяемость и гибкость архитектуры, а во вторых - на упрощение рефакторинга и повышение ПОВТОРНОГО использования.
Как? Поясню - чуть позже.
Собственно это станет видно - когда мы перейдём к оставшимся прецедентам.
(Тут "синергия" видна ИМЕННО для СЛОЖНЫХ систем и для систем в которых есть МНОЖЕСТВО "похожих", но в то же время "не похожих" прецедентов)
.....
Теперь вернёмся к списку ответственностей (требований) наших прецедентов.
ПРЕДПОЛОЖИМ, что указанные ответственности:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
-- ПОЛНОСТЬЮ реализуются компонентом TDocumentView (ну - ПОВЕЗЛО нам так с указанным компонентом).
Соответственно эти ответственности - мы смело можем считать реализованными (ну по крайней мере - пока требования не поменяются). И мы СМЕЛО можем их вычеркнуть из списка ответственностей, которые нам надо реализовать. Считаем их - УЖЕ реализованными.
Как быть с ОСТАЛЬНЫМИ ответственностями? А именно:
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.
На этот вопрос - мы сейчас попробуем ответить. Реализуя далее наш "большой" прецедент - "работа с документом":
Делать мы это будем тут - http://18delphi.blogspot.com/2013/08/mvc_8.html
to be continued...
Пусть есть несколько "похожих" прецедентов (http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D1%86%D0%B5%D0%B4%D0%B5%D0%BD%D1%82_(UML)):
1. Работа с документом
2. Работа со словарной статьёй
3. Работа со статьёй энциклопедии
4. Обзор изменений документа
5. Сравнение соседних редакций документа
Прецеденты взяты из реальной системы. Надеюсь - этому никто не расстроится. Просто качественно можно говорить об известном предмете, а не о "коне вакууме".
Надеюсь, что с одной стороны - никто не узрит в этом "выдачу технологических секретов", а с другой - "скрытую рекламу". Если бы не эти два обстоятельства - писать было бы сильно проще.
Этап сбора и описания требований - опускаю. Этой теме можно посвятить отдельную статью (если интересно). Пока предположим, что требования собраны и описаны. И частично - детализированы.
Прецеденты - РАЗНЫЕ, но по функциональности - ПЕРЕСЕКАЮТСЯ.
Что у них общего? Это - "работа с документом" в том или ином виде.
Понятное дело, что в реализации этих прецедентов - есть много общего, но и есть различия.
Понятное дело, все эти прецеденты визуализуются РАЗЛИЧНЫМИ формами отображения.
Понятно, что эти формы - родственны, но при этом - они и различаются.
Что делать? Как будем проектировать функционал этих прецедентов?
Наследование? Визуальное наследование? Агрегация? Выделение бизнес-логики в отдельный слой/слои?
Хочется немножко рассказать на эту тему.
Для начала надо наверное выделить ОБЩИЕ ответственности для данных прецедентов:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.
пока - ограничимся...
Теперь попытаемся выделить функциональные различия данных прецедентов.
Для начала поймём - как эти прецеденты - визуализируются.
На самом деле каждый прецедент (отображаемый для определённости "формой" - наследником от TForm) распадается на несколько "зон активностей" (или "зон пользовательского фокуса"). На самом деле эти "зоны" (забегая вперёд) это отдельные вложенные прецеденты. На эту тему я немножко "закинул удочку" тут - http://18delphi.blogspot.com/2013/08/usecase.html.
Каждая из зон характеризуется тем, что у них у каждой может быть СОБСТВЕННЫЙ набор Toolbar'ов, каждая может принимать ОТДЕЛЬНО фокус ввода, каждая имеет СОБСТВЕННОЕ контекстное меню, каждая может ВЛИЯТЬ на главное (командное) меню приложения.
Каждая из зон может обладать СОБСТВЕННЫМ состоянием, которое может записываться в историю работы приложения.
Предположим, что распадаются они так (в скобках буду указывать имена "реализующих классов"):
1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).
2. Работа со словарной статьёй (TDictionEntryForm):
а. Русский текст статьи (TRussianTextViewArea).
б. Английский текст статьи (TEnglishTextViewArea).
в. Список словарных статей (TDictionEntriesViewArea).
3. Работа со статьёй энциклопедии (TEncyclopediaForm):
а. Собственно "текст статьи" (TArcticleViewArea).
б. Оглавление энциклопедии (TEncyclopediaContentsViewArea).
4. Обзор изменений документа (TDocumentReviewForm):
а. Собственно "текст обзора документа" (TDocumentReviewTextViewArea).
5. Сравнение соседних редакций документа (TEditionsCompareForm):
а. Текст предыдущей редакции документа (TPrevEditionTextViewArea).
б. Текст "актуальной" редакции документа (TActualEditionTextViewArea).
Что это за классы TXXXViewArea и каково их место в иерархии того же VCL - мы поговорим - чуть позже.
Если вы хотите аналогию из "классического" Delphi и VCL, то скажем так, что TXXXViewArea - "похожи" на TFrame. Именно - ПОХОЖИ. Но НЕ ЯВЛЯЮТСЯ таковыми. Но ПОКА - так думать будет понятно (надеюсь).
Далее надо отметить, что каждый прецедент имеет "точку входа". Это нечто, что принимает "данные для конструирования прецедента" и возвращает собственно ЭКЗЕМПЛЯР прецедента.
В терминах "классики" это - конструктор класса (формы). Но если забегать вперёд и думать об "инъекции зависимостей" (http://ru.wikipedia.org/wiki/Dependency_Injection), то это - фабрика. Фабрика прецедента.
Но! ПОКА забудем о фабриках. До поры до времени.
ПОКА будем считать, что "точка входа" в прецедент это конструктор объекта в терминах "классики".
Также ПОКА забудем о сигнатуре конструктора TForm (и TComponent) и что на самом деле он выглядит так - Create(anOwner : TComponent).
Также ПОКА забудем о "геометрии" и размещении форм (визуализующих прецеденты) в пространстве, а также о свойстве Parent.
Об этом мы поговорим чуть позже. Обещаю.
ПОКА будем считать, что "точка входа" выглядит примерно так CreateUseCase(aData : TUseCaseData).
Где TUseCaseData - некоторый базовый (в пределе - шаблонный) класс, обеспечивающий связь данных прецедента и собственно логики и визуализации прецедента.
Забегая вперёд - отметим, что это скорее всего не класс, а - интерфейс. Но на данном этапе - нам ПОКА это не важно.
Почему ИНТЕРФЕЙС? Потому, что - ПОДСЧЁТ ССЫЛОК. Пока - это - не важно. Но позже - я остановлюсь и на этом моменте.
(Для тех немногих, кто знаком с фреймворком VCM скажу, что TUseCaseData - это собственно и есть IsdsXXX).
Пока дело обстоит так. Есть "визизуализация прецедента" - TXXXForm и "данные прецедента" - TXXXUseCaseData.
TXXXForm обладает конструктором CreateUseCase, с сигнатурой - CreateUseCase(aData : TXXXUseCaseData).
Давайте теперь посмотрим - какие "данные прецедентов" нам понадобятся для визуализации описанных выше прецедентов.
Тут ПОКА - всё очень просто. Отображение тут - 1:1. Т.е. "визуализация прецедента" TXXXForm требует "данных прецедента" TXXXUseCaseData.
Хочется отметить тот факт, что в описываемой "модели" - "данные прецедента" - являются КОНСТАНТНЫМИ. Т.е. они не меняют своё состояние с момента своего рождения до момента своей смерти. Это означает, что собственно прецедент (и его визуализация) могут рассматривать эти данные как инвариант на всём времени своей жизни. Если "данные прецедента" надо поменять (например переходим из одного документа в другой), то это означает, что надо СМЕНИТЬ текущий прецедент. Создав НОВЫЙ экземпляр, подав в "точку входа" (конструктор/фабрику) новые "данные прецедента". Этот ВАЖНЫЙ факт - мы тоже - обсудим. Чуть позже.
Тут бы уже можно было бы нарисовать модель прецедентов и их данных в терминах UML, но к сожалению - инструмент у меня недоступен - посему "буду писать текстом". Пока. А модель - нарисую чуть позже.
Итак - посмотрим на описанные выше прецеденты и их данные.
Пусть запись вида - TXXXForm <=| TXXXUseCaseData - означает "данные TXXXUseCaseData - могут порождать экземпляр прецедента TXXXForm". Т.е. что данные совместимы с входным параметром соответствующего прецедента.
Итак - прецеденты и их данные:
1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData
Теперь собственно рассмотрим - "что же такое эти TXXXUseCaseData":
Как мы уже "договорились" - они все наследуются от TUseCaseData.
Т.е. как-то так (ПОКА):
1. Работа с документом - TDocumentForm <=| TDocumentUseCaseData = class(TUseCaseData)
2. Работа со словарной статьёй - TDictionEntryForm <=| TDictionEntryUseCaseData = class(TUseCaseData)
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=| TEncyclopediaUseCaseData = class(TUseCaseData)
4. Обзор изменений документа - TDocumentReviewForm <=| TDocumentReviewUseCaseData = class(TUseCaseData)
5. Сравнение соседних редакций документа - TEditionsCompareForm <=| TEditionsCompareUseCaseData = class(TUseCaseData)
Это - "крупными мазками".
Теперь поговорим об их наполнении.
Мы уже говорили о TXXXViewArea. "Активных зонах".
И "по индукции" было бы логично раз TXXXUseCaseData порождает TXXXForm, то должен найтись TXXXViewAreaData, который порождает TXXXViewArea.
Про "геометрию", Owner'ов и Parent'ов - опять же - ПОКА - не говорим.
Считаем, что TXXXViewArea имеет "конструктор" CreateViewArea(aData : TXXXViewAreaData).
Откуда берутся эти самые TXXXViewAreaData?
В описываемой модели - они "висят" в виде read-only-property на TXXXUseCaseData. И наследуются (забегая вперёд) от TViewAreaData.
(Для тех немногих, кто знаком с VCM скажу TXXXViewAreaData это ни что иное как IdsXXX)
И тогда картина описываемых прецедентов приобретает следующий вид:
1. Работа с документом - TDocumentForm <=|
TDocumentUseCaseData = class(TUseCaseData) property DocumentText: TDocumentTextViewAreaData; property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData) property DocumentStructure: TDocumentStructureViewAreaData; property DocumentPictures: TDocumentPicturesViewAreaData; end;//TDocumentContentsViewAreaData property DocumentIncomingLinks: TDocumentIncomingLinksViewAreaData; property DocumentOutgoingLinks: TDocumentOutgoingLinksViewAreaData; property DocumentAnnotation: TDocumentAnnotationViewAreaData; end;//TDocumentUseCaseData
2. Работа со словарной статьёй - TDictionEntryForm <=|
TDictionEntryUseCaseData = class(TUseCaseData) property RussianText: TRussianTextViewAreaData; property EnglishText: TEnglishTextViewAreaData; property DictionEntries: TDictionEntriesViewAreaData; end;//TDictionEntry
3. Работа со статьёй энциклопедии - TEncyclopediaForm <=|
TEncyclopediaUseCaseData = class(TUseCaseData) property Arcticle: TArcticleViewAreaData; property EncyclopediaContents: TEncyclopediaContentsViewAreaData; end;//TEncyclopediaUseCaseData
4. Обзор изменений документа - TDocumentReviewForm <=|
TDocumentReviewUseCaseData = class(TUseCaseData) property DocumentReviewText: TDocumentReviewTextViewAreaData; end;//TDocumentReviewUseCaseData
5. Сравнение соседних редакций документа - TEditionsCompareForm <=|
TEditionsCompareUseCaseData = class(TUseCaseData) property PrevEditionText: TPrevEditionTextViewAreaData; property ActualEditionText: TActualEditionTextViewAreaData; end;//TEditionsCompareUseCaseData
Итак - мы ПОКА имеем ЧЕТЫРЕ основных понятия - TXXXForm (прецедент), TXXXViewArea (активная зона/вложенный прецедент), TXXXUseCaseData (данные прецедента) и TXXXViewAreaData (данные активной зоны).
Теперь давайте более детально спроектируем один прецедент. Самый "большой". Первый. "Работа с документом":
Ранее мы ПОКА договорились, что TXXXViewArea это "нечто вроде TFrame". Пока этих знаний - нам достаточно.
Но кто же реально визуализирует данные и осуществляет интерактив с пользователем?
Этот момент - мы сейчас более-менее подробно - разберём.
Итак - наш прецедент "работа с документом" с точки зрения представления пользователю ПОКА выглядит вот так:
1. Работа с документом (TDocumentForm):
а. Собственно "текст документа" (TDocumentTextViewArea).
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea).
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea).
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea).
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea).
д. Аннотация к документу (TDocumentAnnotationViewArea).
Давайте разберёмся - какие конечные компоненты визуализируют те или иные "активные зоны".
Пусть из требований следует что:
а. Собственно "текст документа" (TDocumentTextViewArea) - отображается в виде "текста с гиперссылками и прочим оформлением".
б. Оглавление документа (TDocumentContentsViewArea):
б.1 Список структурных единиц документа (TDocumentStructureViewArea) - отображается в виде "древесной структуры".
б.2 Список иллюстраций в документе (TDocumentPicturesViewArea) - отображается в виде "одноуровневого списка".
в. Список входящих ссылок на документ (TDocumentIncomingLinksViewArea) - отображается в виде "одноуровневого списка".
г. Список исходящих ссылок из документа (TDocumentOutgoingLinksViewArea) - отображается в виде "одноуровневого списка".
д. Аннотация к документу (TDocumentAnnotationViewArea)- отображается в виде "текста с гиперссылками и прочим оформлением" (как и сам текст документа).
Итак мы ПОКА имеем три вида отображения данных:
1. "текст с гиперссылками и прочим оформлением".
2. "древесная структура".
3. "список".
(Не могу не отметить, что для БОЛЬШИНСТВА бизнес-приложений этих способов представления данных - БОЛЕЕ чем достаточно)
Пусть у нас УЖЕ ЕСТЬ компоненты, которые умеет отображать структуры данных в заданном виде:
1. "текст с гиперссылками и прочим оформлением" - TDocumentView.
2. "древесная структура" - TTreeView.
3. "список" - TListView.
Тогда - будем ПОКА считать, что в каждую TXXXViewArea (которая ПОКА по договорённости является TFrame) - вставлен - соответствующий компонент. Например как alClient (это для любителе Delphi). Больше про "геометрию", Owner'а и Parent'а - опять же - ПОКА не говорим.
Теперь как выглядит описание прецедента с точки зрения представления?
А вот так:
type TDocumentForm = class(TForm) DocumentTextViewArea : TDocumentTextViewArea = class(TFrame) Text : TDocumentView; end;//TDocumentTextViewArea DocumentContentsViewArea: TDocumentContentsViewArea = class(TFrame) DocumentStructureViewArea: TDocumentStructureViewArea = class(TFrame) Tree : TTreeView; end;//TDocumentStructureViewArea DocumentPicturesViewArea: TDocumentPicturesViewArea = class(TFrame) List : TListView; end;//TDocumentPicturesViewArea end;//TDocumentContentsViewArea DocumentIncomingLinksViewArea: TDocumentIncomingLinksViewArea = class(TFrame) List : TListView; end;//TDocumentIncomingLinksViewArea DocumentOutgoingLinksViewArea: TDocumentOutgoingLinksViewArea = class(TFrame) List : TListView; end;//TDocumentOutgoingLinksViewArea DocumentAnnotationViewArea: TDocumentAnnotationViewArea = class(TFrame) Text : TDocumentView; end;//TDocumentAnnotationViewArea end;//TDocumentForm
Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:
type TDocumentForm = class(TForm) TDocumentTextViewArea = class(TFrame) Text : TDocumentView; end;//TDocumentTextViewArea DocumentTextViewArea : TDocumentTextViewArea; TListViewArea = class(TFrame) List : TListView; end;//TListViewArea DocumentContentsViewArea: TDocumentContentsViewArea = class(TFrame) DocumentStructureViewArea: TDocumentStructureViewArea = class(TFrame) Tree : TTreeView; end;//TDocumentStructureViewArea DocumentPicturesViewArea: TListViewArea; end;//TDocumentContentsViewArea DocumentIncomingLinksViewArea: TListViewArea; DocumentOutgoingLinksViewArea: TListViewArea; DocumentAnnotationViewArea: TDocumentTextViewArea; end;//TDocumentForm
Но теперь встаёт вопрос - "откуда же берутся данные для соответствующих компонент"? Как TXXXUseCaseData и TXXXViewAreaData коррелируют с данными для конечных компонентов?
Это вопрос - мы сейчас и разберём.
В общем - всё - по индукции.
TXXXUseCaseData - содержит в себе TXXXViewAreaData в виде read-only-property, значит - логично предположить, что TXXXViewAreaData содержит в себе TXXXViewData в виде всё тех же read-only-property.
В общем - так оно и есть.
Картина приобретает следующий вид:
type TDocumentUseCaseData = class(TUseCaseData) property DocumentText: TDocumentTextViewAreaData = class(TViewAreaData) property Text: TDocument; end;//TDocumentTextViewAreaData property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData) property DocumentStructure: TDocumentStructureViewAreaData = class(TViewAreaData) property DocumentStructure : TTree; end;//TDocumentStructureViewAreaData property DocumentPictures: TDocumentPicturesViewAreaData = class(TViewAreaData) property Pictures : TList; end;//TDocumentPicturesViewAreaData end;//TDocumentContentsViewAreaData property DocumentIncomingLinks: TDocumentIncomingLinksViewAreaData = class(TViewAreaData) property IncomningLinks : TList; end;//TDocumentIncomingLinksViewAreaData property DocumentOutgoingLinks: TDocumentOutgoingLinksViewAreaData = class(TViewAreaData) property OutgoingLinks : TList; end;//TDocumentOutgoingLinksViewAreaData property DocumentAnnotation: TDocumentAnnotationViewAreaData = class(TViewAreaData) property Annotation: TDocument; end;//TDocumentAnnotationViewAreaData end;//TDocumentUseCaseData
(Для тех кто в курсе про VCM: TDocument это и есть TXXXDocumentContainer)
И ОПЯТЬ! Проведём предварительный рефакторинг получившейся картины путём выделения "похожих" классов:
type TDocumentUseCaseData = class(TUseCaseData) TDocumentTextViewAreaData = class(TViewAreaData) property Text: TDocument; end;//TDocumentTextViewAreaData property DocumentText: TDocumentTextViewAreaData; TListViewAreaData = class(TViewAreaData) property List : TList; end;//TListViewAreaData property DocumentContents: TDocumentContentsViewAreaData = class(TViewAreaData) property DocumentStructure: TDocumentStructureViewAreaData = class(TViewAreaData) property DocumentStructure : TTree; end;//TDocumentStructureViewAreaData property DocumentPictures: TListViewAreaData; end;//TDocumentContentsViewAreaData property DocumentIncomingLinks: TListViewAreaData; property DocumentOutgoingLinks: TListViewAreaData; property DocumentAnnotation: TDocumentTextViewAreaData; end;//TDocumentUseCaseData
Идея понятна?
Вот тут наверное подходящий момент, чтобы остановиться и спросить - "а зачем так сложно?"
Я сейчас постараюсь это пояснить.
Во-первых - мне кажется, что в общем - НИЧУТЬ не сложно. Процедура - ФОРМАЛЬНАЯ. И устроена - как "матрёшка", что само по себе (меня лично - радует).
Выделяем прецеденты, потом описываем вложенные прецеденты, потом описываем данные прецедентов и вложенных прецедентов. Потом - детализируем прецеденты и получаем компоненты, которые взаимодействуют с пользователем, потом - описываем - данные для компонентов. Процедура - ФОРМАЛЬНАЯ и рекурсивная.
А во-вторых - вся эта "сложность" во-первых направлена на взаимозаменяемость и гибкость архитектуры, а во вторых - на упрощение рефакторинга и повышение ПОВТОРНОГО использования.
Как? Поясню - чуть позже.
Собственно это станет видно - когда мы перейдём к оставшимся прецедентам.
(Тут "синергия" видна ИМЕННО для СЛОЖНЫХ систем и для систем в которых есть МНОЖЕСТВО "похожих", но в то же время "не похожих" прецедентов)
.....
Теперь вернёмся к списку ответственностей (требований) наших прецедентов.
ПРЕДПОЛОЖИМ, что указанные ответственности:
1. Отображение документа.
2. Скроллирование документа.
3. Навигация курсором в рамках текста документа.
4. Выделение участков текста документа.
5. Копирование выделенных участков в буфер обмена.
-- ПОЛНОСТЬЮ реализуются компонентом TDocumentView (ну - ПОВЕЗЛО нам так с указанным компонентом).
Соответственно эти ответственности - мы смело можем считать реализованными (ну по крайней мере - пока требования не поменяются). И мы СМЕЛО можем их вычеркнуть из списка ответственностей, которые нам надо реализовать. Считаем их - УЖЕ реализованными.
Как быть с ОСТАЛЬНЫМИ ответственностями? А именно:
6. Печать и предварительный просмотр всего документа.
7. Печать и предварительны просмотр выделенных участков документа.
8. Переход по ссылкам. Ссылки бывают - внутренние (внутри данного прецедента) и внешние (между различными прецедентами).
9. Логирование функций работы с документом.
10. Экспорт всего документа в определённые форматы.
11. Экспорт выделенного участка текста в определённые форматы.
На этот вопрос - мы сейчас попробуем ответить. Реализуя далее наш "большой" прецедент - "работа с документом":
Делать мы это будем тут - http://18delphi.blogspot.com/2013/08/mvc_8.html
to be continued...
Продолжать? Или - "занудно"?
ОтветитьУдалитьПродолжать однозначно :)
ОтветитьУдалитьА еще хочется увидеть реализацию в коде а не только отрывки...
(пусть даже это макет но хочется чтобы он "работал, жил").
Насколько я понимаю в итоге мы доползем до кодогенерации :)
Сегодня вечером - продолжу.
УдалитьВот наконец-то пошла конкретика. Ну на интуитивном уровне я что-то подобное уже начал представлять. Продолжайте, это очень полезно, пусть и не в привычной для массы форме.
ОтветитьУдалитьО! Николай :-) Очень ПРАВИЛЬНЫЙ от вас комментарий... :-)
УдалитьКому-то - реально интересно :-) "Ну на интуитивном уровне" :-) Я же сам - "интуичу"..
Я же - НИЧЕГО не изобрёл.. Я же просто 25-ть лет "синтезирую"...
Да и НИЧЕГО - непривычного :-) - MVC.... Только - своя итертрепация.. :-) Простите - интерпретация..
А если вы в Москве - могу и реально показать :-) "На пальцах" :-)
Николай, ведь идея УЖЕ понятна? :-)
УдалитьВедь СЛОЖНО, что-то РЕАЛЬНО новое придумать :-) Да и - незачем...
УдалитьДа, идея понятна. Уже после опубликованного интервью с Всеволодом у меня была ночь, где я в полудрёме переделывал наши проекты (но блин, там такой объём кода...)
УдалитьВ общем-то идея лежит на поверхности... как всё гениальное. Жалко, что своим путём к ней быстро прийти не получилось.
А я из Питера, да. Но в Москву по долгу службы раз в год езжу.. Я Ваше приглашение запомню, спасибо :)
"Да, идея понятна."
УдалитьЭто правда - ЗДОРОВО!
"Уже после опубликованного интервью с Всеволодом у меня была ночь, где я в полудрёме переделывал наши проекты (но блин, там такой объём кода...)"
Нашего интервью?
"В общем-то идея лежит на поверхности... как всё гениальное. Жалко, что своим путём к ней быстро прийти не получилось." - так оно обычно и бывает :-) Я же говорю - "я сам IUnknown придумал" :-)
"А я из Питера, да. Но в Москву по долгу службы раз в год езжу.. Я Ваше приглашение запомню, спасибо :)"
Питер - это близко. 6-ть часов :-) Я бы съездил "на марсово поле посмотреть"... Если есть интерес :-)
Заодно - может и с Янковским лично познакомлюсь :-)
Удалить> Нашего интервью?
УдалитьИменно. До этого я с интересом просматривал обрывки из Вашего блога, но в общую картинку оно не торопилось сливаться...
> Питер - это близко
Ну всё (или почти всё) в нашем мире относительно :)
Может быть с Севой обсудить возможность проведения семинара... или лучше вебинара (так доступнее для большинства)?
> Если есть интерес
Интерес есть. Даже не в рабочее время.
> Нашего интервью?
УдалитьИменно. До этого я с интересом просматривал обрывки из Вашего блога, но в общую картинку оно не торопилось сливаться...
Отрадно слышать :-) Мой блог "заметки на полях" :-) Мало кто понимает :-) Всеволод - молодец - "вытащил на свет"...
Идеи-то - БАНАЛЬНЫЕ...
> Питер - это близко
Ну всё (или почти всё) в нашем мире относительно :)
Может быть с Севой обсудить возможность проведения семинара... или лучше вебинара (так доступнее для большинства)?
Хм.. Семинар или вебинар - это конечно - почётно :-) Но я лучше в курилке устно выступаю :-)
Хотя... Если тезисы написать...
> Если есть интерес
Интерес есть. Даже не в рабочее время.
Да когда угодно :-) Если интерес есть...
Я же не для того, чтобы "в карман положить" :-) Я же просто - "поделиться" :-) А то - 25-ть лет "креативлю".. а - "бестолку"...
"Именно. До этого я с интересом просматривал обрывки из Вашего блога, но в общую картинку оно не торопилось сливаться.." - понимаете.. слишком много информации "в серых клеточках" :-) чтобы её систематизированно изложить...
УдалитьЯ уже многим говорил - "давайте - я вам расскажу, а вы напишете" :-)
Всеволод - вод такие вопросы сегодня задал, что я опять "пошёл думать"...
В поисках, с какого инструмента начать, вот на такую страничку вышел:
ОтветитьУдалитьhttp://en.wikipedia.org/wiki/List_of_UML_tools
Из приведённого там списка более-менее перспективным мне кажется только WhiteStarUML.
Удалить