Часть IV.1. Engines
АрхивПрограммазм (архив)Четвертая статья из цикла, посвященного распределенным программным системам, рассказывающая о последнем типе объектов из семейства ECC - Engines.
Эта статья будет последней по теме создания распределённых программных комплексов и систем и речь в ней пойдёт о последнем типе объектов из семейства ECC — Engines. В предыдущих статьях было рассказано:
- что такое ECC
- в чём заключаются преимущества применения этой методики
- каким образом можно программировать Классы (Classes) и Коллекции (Collections)
- что за трудности ожидают разработчиков при применении ECC в реальных задачах
В этой я расскажу о том, для чего нужны Engines, как они работают с БД (коснёмся и data layer), а главное — будет показано, как на основе технологий MS можно организовать простое взаимодействие между объектами, с тем, чтобы они могли взаимодействовать и оповещать друг друга об изменениях в своём состоянии.
Engines
Основное и единственное назначение Engine для класса — создавать коллекции. Дело в том, что одно из назначений ECC заключается в обеспечении концептуальной чистоты разработки. Это значит вот что — один из важнейших лозунгов ECC звучит так: «Ни один класс не может создавать экземпляры вышестоящих классов своей сущности». Это со всей отчётливостью видно на рис 1.
Рис 1. Кто что может создавать
Попутно заметим, что разрешены только те взаимодействия, которые явно показаны на рисунке. То есть, Class не может создавать ни коллекции, ни Engine; Collection ни при каких обстоятельствах не может создавать Engine. Одной из причин подобных строгостей является желание избежания круговых ссылок. Известно, что при должной ловкости рук можно избавиться от любых круговых ссылок, но оно нам нужно? Важно также и то, что все эти ограничения не распространяются на классы, не принадлежащие одной сущности. То есть вот когда: предположим, что моделируется электролизёр [1] (Electrolyser). Говоря очень грубо (а тонкости металлургии ЦМ здесь никто не оценит), электролизёр имеет в своём составе 2 ключевых компонента: анод (Anode) и катод (Cathode). Очевидно, что при создании экземпляра класса Electrolyser должен создавать экземпляры классов Anode и Cathode. И между прочим, никто не может в этом помешать! Внутри концептуально низшего класса Electrolyser можно обращаться к классам Engine и Collection классов Anode и Cathode. То есть, вот так (в синтаксисе VC++):
STDMETHODIMP CElectrolyser::get_Cathode(IColCathodes* *pVal)
{
IIOPECathodesEngine * caeng = 0;
HRESULT hr = CoCreateInstance( __uuidof(IOPECathodesEngine),
NULL, CLSCTX_ALL,
__uuidof(IIOPECathodesEngine),
(void**)&caeng);
if (FAILED(hr))
return E_FAIL;
IColCathodes * col = 0;
hr = caeng->GetCathodes(DEBUG_SEC_DESC, m_CathodeNumber, &col);
if (FAILED(hr) || !col)
{
caeng->Release();
return E_FAIL;
}
*pVal = col;
caeng->Release();
return S_OK;
}
Получение коллекции катодов (содержащей только один элемент, имеющий ключ m_CathodeNumber) внутри метода CElectrolyser. Можно использовать объекты классов, стоящих более высоко в иерархии только в том случае, когда они относятся к другой сущности.
Но мы отвлеклись. Вернёмся к тому, что такое Engines.
Engine's Valves
|
|
|
|
|
|
Все методы возвращают коллекции соответствующих элементов, то есть, имеют приблизительно следующую сигнатуру:
interface IIOPEElectrolysersEngine : IDispatch
{
[id(1), helpstring("method GetElectolysersList")]
HRESULT GetElectolysersList(BSTR SecurityToken, IColElectrolysers** FList);
[id(2), helpstring("method GetElectrolysersByNumber")]
HRESULT GetElectrolysersByNumber(BSTR SecurityToken,
LONG fnumber,
IColElectrolysers** FList);
[id(3), helpstring("method GetElectrolysersById")]
HRESULT GetElectrolysersById(BSTR SecurityToken,
LONG fid, IColElectrolysers** FList);
};
Здесь видно, что достаточно лишь двух методов внутри класса Engine. В самом деле всё многообразие типов запросов к данным можно разбить на 2 группы:
- запросы, возвращающие 1 элемент (типа SELECT @x = MAX(some_field) FROM sometable, или SELECT field_1, field_2 FROM some_table WHERE field_3 = @x)
- запросы, возвращающие несколько элементов, группу (SELECT field_1, field_2 FROM sometable)
Ясно, что могут быть и другие методы, возвращающие запрошенные данные. Другими словами, внутрь Engine помещается код, читающий данные из соответствующего хранилища.
Кое-что о данных
В следующей сводной таблице показано, чем же занимается каждый из классов некой сущности.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Из этой таблицы видно, что непосредственно с хранилищем данных (БД, например) работают Collection и Engine. В стародавние времена для работы с БД предлагалась масса библиотек, некоторые из них были даже ничего, но эти самые времена изменились, и теперь в Windows царствует стандарт. Является ли это борьбой с конкурентами, или просто проявлением доброй воли Джима Олчина — пусть этим вопросом займётся полусумасшедший судья Джексон, нам некогда — мы зарабатываем деньги. А чтобы заработать их быстрей, нам настоятельно рекомендуют ADO. Всем известно, что одним из наиболее употребительных способов переноса данных от объекта к объекту в ADO являются объекты класса Recordset. Это удобно, надёжно и дёшево, поскольку Microsoft постоянно работает над повышением производительности (в версии 2.5 был достигнут даже своеобразный рекорд: применение ADO совместно с VB давало исполняемый код более чем на 10% быстрый, чем в комбинации «ADO и VC++»). Словом, это хорошо всем, кроме одного: взять реальную, хранящуюся в БД информацию без подключения к серверу БД невозможно. А всякая работа с сервером БД напрямую из любого объекта ECC является нарушением основного принципа компонентного ПО: максимальной независимости фрагментов. Конечно, можно не обращать никакого внимания на этот «пустяк» и в небольших проектах это сойдёт с рук, но ведь мы ответственны за свой код перед теми, кто придёт после нас, не так ли? Код должен оставаться ясным, чистым и соответствовать избранной стратегии разработки. И раз уж избран путь на ECC, то пора выделить уровень БД.
IODLConnection
Механизм взаимодействия объектов c БД в рамках ECC можно представить следующим образом:
Рис 2. Механизм работы объектов ECC с сервером БД
Замечание: как показывает опыт, модель ЕСС весьма жизнеспособна, а её использование продуктивно и при видах источников и хранилищ данных, отличных от серверов БД. Т.е., этой же методикой можно воспользоваться и в случаях, когда в качестве СУБД используется MS Access или даже MS FoxPro [2].
Внутри компонента с этим замысловатым именем (я дал его просто так, а значит оно Input-Output ('cause it's Data-access Level) Connection содержится вот что (здесь и далее при нажатии на ссылку откроется новое окно с кодом программы — прим.ред.).
Это практически полный код компонента, осуществляющего связь компонентов ECC c БД. Здесь не хватает некоторых второстепенных функций и констант, но смысл, полагаю, понятен и без них. Если отвлечься от всякой мишуры, связанной с кодированием поддержки транзакций, то видно, что компонент «состоит» из одного большого метода Execute. Этот метод — универсальное средство выполнения запросов к БД, имеющих вид хранимых процедур. Методу передаётся имя процедуры, коллекция параметров и переменная для возврата возможного набора строк. В коде видно, что к последним двум параметрам этого метода слева приписано ключевое слово Optional, а сами они имеют тип Variant. Это маленький трюк, необходимый для упрощения процесса кодирования (ради любопытства найдите в MSDN С-эквивалент функции IsMissing). Теперь самое время показать, как этот компонент используется в коде Engine.
Data mining
Итак, что уважаемая публика уже знает к настоящему моменту?
- Engines создают Collections
Даже если объекта класса Engine не смог прочитать ни одного объекта из БД (например, там нет ничего, соответствующего запросу), он всё равно должен вернуть размещённый, хоть и «пустой» (Collection.Count = 0) объекта класса Collection
Engine может создавать Collection, даже если Collection вообще не связана с данными, то есть класс Engine должен иметь метод вроде CreateNewCollection. Помимо прочего это намекает на способы использования методики ECC, в которых работа с хранилищами данных не стоит на первом месте.
- Collections создают и хранят Classes
Коллекция обязана предоставлять быть коллекцией в COM-стиле.
Коллекция должна иметь методы для создания пустого объекта Class и для конструирования такого объекта по данным, являющимся полями уже существующего объекта типа Class
- Classes не могут создать ни Collection, ни Engine своей сущности
Итак, читатель знает почти всё. Осталось лишь показать, каким образом происходит чтение данных из БД. Сделано это будет на примере VC++.
Заголовок вспомогательного класса, служащего корнем иерархии всех «Энжинов» системы
Интерес здесь представляют три метода: GetFullList, GetItem и ExtractFields.
GetFullList
Получение полного списка объектов
GetItem
По-большевистски прямолинейный код методов чтения практически не оставляет вопросов. Видно, что на сервере выполняется запрос, возвращающий (или не возвращающий) набор строк, по которым затем с тупой методичностью создаются объекты класса Class и добавляются в коллекцию Collection. Единственная мелкая уловка заключается в вызове метода ExtractFields, который по причине своей чистой виртуальности лишь обозначен в интерфейсе вспомогательного класса. Реальная же его инкарнация может выглядеть следующим образом:
ExtractFields
Здесь тоже всё очень просто: поля читаются из текущей строки переданного набора строк и тут же превращаются в инициализаторы соответствующих свойств некого объекта класса Class (в данном случае ICasing)
Заключение
Итак, рассказ о применении ECC закончен. Показано, что, как и для чего. Конечно, у тех, кто внимательно читал всё это на протяжении последних трёх статей остались (должны остаться!) вопросы. Например, как организовывать взаимодействие между объектами. Но я ничего не забыл. Всё, что было обещано, но до сих пор не сделано, любой сможет найти во второй части этой статьи.
[1] — Я предупреждал, что примеры немного нестандартные
[обратно к тексту]
[2] — Бойтесь его, бандерлоги!
[обратно к тексту]