Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Shablony_korporativnykh_prilozheniy_Fauler_M.docx
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
4 Мб
Скачать
☆

Модуль таблицы (Table Module)

Объект, охватывающий логику обработки всех записей хранимой или виртуальной таблицы базы данных

Одна из ключевых предпосылок объектной модели — сочетание элементов данных и пользующихся ими функций. Традиционный объектно-ориентированный подход ос­нован на концепции объектов с идентификационными признаками в совокупности с требованиями модели предметной области (Domain Model). Если, например, речь идет о классе, представляющем сущность "служащий", любой экземпляр класса соот­ветствует определенному служащему; коль скоро есть ссылка на объект, отвечающий служащему, с ним легко выполнять все необходимые операции, собирать информацию и следовать в направлении связей с другими объектами.

Одна из проблем модели предметной области заключается в сложности создания ин­терфейсов к реляционным базам данных; последние в подобной ситуации приобретают роль эдаких бедных родственников, с которыми никто не желает иметь дела. Поэтому считывание информации из базы данных и запись ее с необходимыми преобразованиями превращается в прихотливую игру ума.

Типовое решение модуль таблицы предусматривает создание по одному классу на каждую таблицу базы данных, и единственный экземпляр класса содержит всю логику обработки данных таблицы. Основное отличие модуля таблицы от модели предметной области состоит в том, что если, например, приложение обслуживает множество зака­зов, в соответствии с моделью предметной области придется сконструировать по одному объекту на каждый заказ, а при использовании модуля таблицы понадобится всего один объект, представляющий одновременно все заказы.

Принцип действия

Сильная сторона решения модуль таблицы заключается в том, что оно позволяет со­четать данные и функции для их обработки и в то же время эффективно использовать ресурсы реляционной базы данных. На первый взгляд модуль таблицы во многом на­поминает обычный объект, но отличается тем, что не содержит какого бы то ни было упоминания об идентификационном признаке объекта. Если, скажем, требуется полу­чить адрес служащего, для этого применяется метод anEmployeeModule.getAddress(long employeeID). В том случае, когда необходимо выполнить операцию, касающую­ся определенного служащего, соответствующему методу следует передать ссылку на идентификатор, значение которого зачастую совпадает с первичным ключом служащего в таблице базы данных.

Модулю таблицы, как правило, отвечает некая табличная структура данных. Подобная информация обычно является результатом выполнения SQL-запроса и сохраняется в ви­де множества записей (Record Set). Модуль таблицы предоставляет обширный арсе­нал методов ее обработки. Объединение функций и данных обеспечивает многие пре­имущества модели инкапсуляции.

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

Наиболее очевидный пример связан с использованием отдельных модулей таблицы для каждой (хранимой) таблицы базы данных. Кроме того, можно сконструировать моду­ли таблицы для любых достойных SQL-запросов и виртуальных таблиц.

Конкретный модуль таблицы может принимать вид экземпляра класса или набора ста­тических методов. Достоинство первого варианта состоит в том, что он позволяет ини­циировать модуль данными существующего множества записей, чаще всего получаемого в результате обработки SQL-запроса. Для манипуляции записями применяются методы класса. Не исключается и возможность создания иерархии наследования, когда, скажем, определяются базовый класс с описанием контракта общего вида и целое семейство про­изводных классов, отвечающих частным разновидностям контрактов.

Рис. 9.4. Несколько модулей таблицы способны обращаться к одному и тому же множеству записей

Модуль таблицы способен содержать методы-оболочки, представляющие запросы к базе данных. Альтернативой служит шлюз таблицы данных (Table Data Gateway). Недостаток последнего обусловлен необходимостью конструирования дополнительного класса, а преимущество заключается в возможности применения единого модуля таблицы для данных из различных источников, поскольку каждому отвечает собственный шлюз таблицы данных.

Шлюз таблицы данных позволяет структурировать информацию в виде множества записей, которое затем передается конструктору модуля таблицы в качестве аргумента (рис. 9.5). Если необходимо использовать несколько модулей таблицы, все они могут быть созданы на основе одного и того же множества записей. Затем каждый модуль таб­лицы применяет к множеству записей функции бизнес-логики и передает измененное множество записей слою представления для отображения и редактирования информа­ции средствами графических табличных элементов управления. Последние не "осве­домлены", откуда поступили данные — непосредственно от реляционной СУБД или от промежуточного модуля таблицы, который успел осуществить их предварительную об­работку. По завершении редактирования информация возвращается модулю таблицы для проверки перед сохранением в базе данных. Одно из преимуществ подобного сти­ля — возможность тестирования модуля таблицы путем "искусственного" создания множества записей в памяти без обращения к реальной таблице базы данных.

Слово "таблица" в названии типового решения подчеркивает, что в приложении пре­дусматривается по одному модулю таблицы для каждой хранимой таблицы базы данных. Это правда, но не вся. Полезно также иметь модули таблицы для общеупотребительных виртуальных таблиц и запросов, поскольку структура модуля таблицы на самом деле не зависит напрямую от структуры физических таблиц базы данных, а определяется в ос­новном характеристиками виртуальных таблиц и запросов, используемых в приложении.

Рис. 9.5. Схема взаимодействия слоев кода с модулем таблицы

Назначение

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

Модуль таблицы, однако, не позволяет воспользоваться всей мощью объектного подхода к организации сложной бизнес-логики. Вам не удастся создать прямые связи от объекта к объекту, да и механизм полиморфизма действует в этих условиях не без­укоризненно. Поэтому для реализации особо изощренной логики предпочтительнее модель предметной области. По существу, проблема сводится к поиску компромисса между способностью модели предметной области к эффективному отображению биз­нес-логики и простотой интеграции кода приложения и реляционных структур дан­ных, обеспечиваемой модулем таблицы.

Если объекты модели предметной области относительно точно отвечают таблицам ба­зы данных, возможно, целесообразнее применить модель предметной области совместно с активной записью (Active Record). Модуль таблицы, однако, ведет себя лучше, чем комбинация модели предметной области и активной записи, если разные части приложе­ния основаны на общей табличной структуре данных. Впрочем, в среде Java, например, модуль таблицы пока не пользуется популярностью, хотя с распространением модели множеств записей ситуация, возможно, и изменится.

Чаще всего образцы использования модуля таблицы приходится видеть в проектах на основе архитектуры Microsoft COM. В технологии СОМ (и .NET) множество записей представляет собой основное хранилище данных, с которыми оперирует приложение. Множества записей могут передаваться фактическим элементам управления для воспро­изведения информации на экране. Добротный механизм доступа к реляционным дан­ным, представленным в виде множеств записей, реализован в семействе библиотек Mi­crosoft ADO. В подобных случаях модуль таблицы позволяет описать бизнес-логику в хо­рошо структурированном виде.

Пример: определение зачтенного дохода (С#)

Настал черед вновь обратиться к задаче определения зачтенного дохода (см. раздел 9.1.3), но на сей раз в контексте модуля таблицы. Напомню, что цель состоит в вычисле­нии зачтенного дохода ("revenue recognitions") по контракту ("contract") в зависимости от упомянутого в нем типа продукта ("product"). Здесь будем различать три типа продук­тов — текстовый процессор (ниже в примере кода будем ссылаться на него с помощью значения wp (от word processor) перечислимого типа ProductType), электронная таблица (ss — spreadsheet) и СУБД (db — database).

Модуль таблицы основывается на некой структуре данных, обычно реляционной (в будущем, вероятно, на эту роль сможет претендовать модель XML). В нашем случае ре­ляционную схему допустимо изобразить так, как показано на рис. 9.6.

Рис. 9.6. Схема базы данных о контрактах, продуктах и зачтенном доходе

Классы, манипулирующие этими данными, следуют единой форме: каждой таблице отвечает определенный класс модуля таблицы. В архитектуре .NET можно создать объект множества данных, обеспечивающий представление структуры базы данных в памяти. Поэтому имеет смысл конструировать классы, способные оперировать множеством дан­ных. Каждый класс модуля таблицы содержит элемент данных системного .NET-типа DataTable, который соответствует одной из таблиц множества данных. Способность счи­тывать табличную информацию свойственна всем модулям таблицы, ею может обладать даже супертип слоя (Layer Supertype).

class TableModule...

protected DataTable table;

protected TableModule(DataSet ds, String tableName) {

table = ds.Tables[tableName];

}

Конструктор производного класса вызывает конструктор суперкласса и передает на­именование конкретной таблицы.

class Contract...

public Contract (DataSet ds) : base (ds, "Contracts") {}

Это позволяет создать новый модуль таблицы, просто передав множество данных со­ответствующему конструктору, синтаксис которого приведен ниже, и отделить код, кон­струирующий множество данных, от модулей таблицы, что отвечает рекомендациям спе­цификации ADO.NET.

contract = new Contract(dataset);

Очень удобен инструмент индексации, реализованный в С#, который позволяет "добраться" до определенной записи таблицы данных по значению первичного ключа.

class Contract...

public DataRow this [long key] {

get {

String filter = String.Format("ID = {0}", key);

return table.Select(filter)[0];

}

}

Функции первой группы осуществляют вычисление значений зачтенного дохода по контракту и обновление соответствующей таблицы. Получаемые значения зависят от ти­па продукта. Поскольку эти операции основаны преимущественно на данных из таблицы контрактов, я решил добавить в класс Contract надлежащий метод.

class Contract...

public void CalculateRecognitions (long contractID) {

DataRow contractRow = this[contractID];

Decimal amount = (Decimal)contractRow["amount"];

RevenueRecognition rr = new RevenueRecognition (table.DataSet);

Product prod = new Product(table.DataSet);

long prodID = GetProductId(contractID);

if (prod.GetProductType(prodID) == ProductType.WP) {

rr.Insert(contractID, amount, (DateTime) GetWhenSigned(contractID));

}else if (prod.GetProductType(prodID) == ProductType.SS) {

Decimal[] allocation = allocate(amount,3);

rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID));

rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).

AddDays(60));

rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).

AddDays(90));

}else if (prod.GetProductType(prodID) == ProductType.DB) {

Decimal[] allocation = allocate(amount,3);

rr.Insert(contractID, allocation[0], (DateTime) GetWhenSigned(contractID));

rr.Insert(contractID, allocation[1], (DateTime) GetWhenSigned(contractID).

AddDays(30));

rr.Insert(contractID, allocation[2], (DateTime) GetWhenSigned(contractID).

AddDays(60));

}else throw new Exception("invalid product id");

}

private Decimal[] allocate(Decimal amount, int by) {

Decimal lowResult = amount / by;

lowResult = Decimal.Round(lowResult,2);

Decimal highResult = lowResult + 0.01m;

Decimal[] results = new Decimal[by];

int remainder = (int) amount % by;

for (int i = 0; i < remainder; i++) results[i] = highResult;

for (int i = remainder; i < by; i++) results[i] = lowResult;

return results;

}

Здесь можно было бы применить типовое решение деньги (Money), но для разно­образия я показал, как работать с типом Decimal. Метод allocate схож с тем, который используется в контексте решения деньги.

Другие классы также необходимо снабдить соответствующими методами. Класс про­дуктов должен "уметь" сообщать о типе конкретного продукта. Этого можно добиться посредством соответствующих перечислимого типа и метода.

public enum ProductType {WP, SS, DB};

class Product...

public ProductType GetProductType (long id) {

String typeCode = (String) this[id]["type"];

return (ProductType) Enum.Parse(typeof(ProductType), typeCode);

}

Метод GetProductType инкапсулирует информацию таблицы типа DataTable. Имеет смысл обеспечить инкапсуляцию всех столбцов данных, а не обращаться к ним напрямую, как я поступал с полем amount таблицы контрактов (хотя использовать мо­дель инкапсуляции, вообще говоря, неплохо, в данном случае я от нее отказался, по­скольку она вступала в противоречие с предположением о том, что различные части сис­темы способны адресовать множество данных непосредственно). Использовать отдель­ные функции доступа к столбцам имеет смысл только тогда, когда необходимо выпол­нять дополнительные операции (например, преобразование строки в значение типа Pro­ductType).

Здесь уместно напомнить и о том, что предпочтительнее пользоваться строго типизи­рованными множествами данных .NET, хотя в этом случае я применил нетипизированное множество.

Еще одна функция связана со вставкой новой записи, описывающей зачтенный доход.

class RevenueRecognition...

public long Insert (long contractID, Decimal amount, DateTime date) {

DataRow newRow = table.NewRow();

long id = GetNextID();

newRow["ID"] = id;

newRow["contractID"] = contractID;

newRow["amount"] = amount;

newRow["date"]= String.Format("{0:s}", date);

table.Rows.Add(newRow);

return id;

}

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

Функции второй группы связаны с суммированием значений всех зачтенных доходов по контракту, полученных на определенную дату. Поскольку вычисления затрагивают таблицу зачтенного дохода, целесообразно определить подходящий метод в классе, отве­чающем этой таблице.

class RevenueRecognition...

public Decimal RecognizedRevenue (long contractID, DateTime asOf) {

String filter = String.Format("ContractID = {0}AND date <= #{1:d}#", contractID,

asOf);

DataRow[] rows = table.Select(filter);

Decimal result = 0m;

foreach (DataRow row in rows) {

result += (Decimal)row["amount"];

}

return result;

}

Этот фрагмент демонстрирует прекрасное средство ADO.NET, позволяющее опреде­лять строку предложения where и выбирать необходимое подмножество записей табли­цы типа DataTable. Ничто не мешает пойти еще дальше и воспользоваться агрегирую­щей функцией.

class RevenueRecognition...

public Decimal RecognizedRevenue2 (long contractID, DateTime asOf) {

String filter = String.Format("ContractID = {0}AND date <= #{1:d}#", contractID,

asOf);

String computeExpression = "sum(amount)";

Object sum = table.Compute(computeExpression, filter);

return (sum is System.DBNull) ? 0 : (Decimal) sum;

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]