Паттерны архитектуры источников данных (PoEAA)
Row Data Gateway (Шлюз к данным записи)
Объект выступает в роли шлюза к отдельной записи в источнике данных. Один экземпляр на одну запись.
Встраивание кода доступа к БД в объекты, хранящиеся в памяти, может привести в некоторым неудобствам. Во-первых, если ваши объекты содержат бизнес-логику, добавление взаимодействия с БД увеличит сложность. Вскоре тестирование станет неудобным, если объекты, хранящиеся в памяти, завязаны на БД. Тесты станут медленнее из-за доступа к БД. Объект шлюза к записи представляется в точности, как запись в БД, но при этом даёт возможность доступа посредством штатных средств языка программирования. Все подробности доступа к БД скрыты за этим интерфейсом.Пример: объект шлюза PersonGateway, данные о записи из таблицы person и методы insert и update, которые позволяют прозрачно работать с записью.Качественная реализация этого паттерна на PHP существует в частности в Zend Framework в классе Zend_Db_Table_Row.
Active Record (Активная запись)
Один объект управляет и данными, и поведением. Большинство этих данных постоянны и их надо хранить в БД. Этот паттерн использует подход - хранение логики доступа к данным в объекте сущности.
Объект является "обёрткой" одной строки из БД или представления, включает в себя доступ к БД и логику обращения с данными.
Пример: объект "Person" содержит данные об одной персоне и методы: добавить, обновить или удалить. По сути паттерн продвигает идею когда модель и логика сосредоточены в 1 классе.
рассмотрим диаграмму класса "Person":
Пример реализации Active record на C#
using System.Data;
using System.Data.SqlClient;
namespace ActiveRecord
{
public class Customer
{
private const string CONNECTION_STRING =
"Data Source=(local);Initial Catalog=DesignPatterns;Integrated Security=True";
public int ID { get; set; }
public string Name { get; set; }
public bool IsPremiumMember { get; set; }
public Customer(int id, string name, bool isPremiumMember)
{
ID = id;
Name = name;
IsPremiumMember = isPremiumMember;
}
// Этот статический метод действует как фабрика объектов для объектов Customer,
// чтение значений из базы данных и создание объекта.
// Итак, код для получения клиента из базы данных может быть:
//
// Customer.GetByID(123);
//
public static Customer GetByID(int id)
{
using(SqlConnection connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
using(SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "SELECT TOP 1 * FROM [Customer] WHERE [ID] = @ID";
command.Parameters.AddWithValue("@ID", id);
SqlDataReader reader = command.ExecuteReader();
if(reader.HasRows)
{
reader.Read();
string name = (string) reader["Name"];
bool isPremiumMember = (bool) reader["IsPremiumMember"];
return new Customer(id, name, isPremiumMember);
}
}
}
return null;
}
public void Save()
{
// Этот метод должен обрабатывать INSERT и UPDATE.
// Или вам нужно создать две отдельные функции и вызывать их при необходимости.
// Этот метод не должен получать параметр с объектом Customer.
// Он находится внутри объекта Customer, поэтому все значения свойств для него уже доступны.
}
public void Delete()
{
using(SqlConnection connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
using(SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "DELETE FROM [Customer] WHERE [ID] = @ID";
command.Parameters.AddWithValue("@ID", ID);
command.ExecuteNonQuery();
}
}
}
}
}
Table Data Gateway (Шлюз к данным таблицы)
Описание:
Объект выступает в качестве шлюза между данными в приложении и в БД. Один объект работает сразу со всеми записями в таблице.
Шлюз к данным таблицы содержит весь SQL для доступа к таблице или представлению (view): selects, inserts, updates, deletes. Идея состоит в том что Table Data Gateway предоставляет "прокси-класс" (шлюз) для доступа к каждой таблицы БД.
Как это работает:
Шлюз табличных данных имеет простой интерфейс, обычно состоящий из нескольких методов поиска для получения данных из базы данных и методов обновления, вставки и удаления. Каждый метод отображает входные параметры в вызов SQL и выполняет SQL для соединения с базой данных. Шлюз табличных данных, как правило, не имеет состояния, поскольку его роль заключается в передаче данных с базы в бизнес логику и обратно.
По сути Table Data Gateway паттерн это прослойка которая отделяет SQL от бизнесс логики.
Когда нужно использовать Table data gateway?
- Шлюз таблиц данных, вероятно, является наиболее простым в использовании интерфейс БД паттерном , поскольку он так хорошо отображается на таблицу базы данных или тип записи. Это также создает естественную точку для инкапсуляции точной логики доступа источника данных.
- Используя Table Data Gateway, у вас будет по одному шлюзудля каждой таблицы в базе данных. Однако для очень простых случаев у вас может быть один шлюз табличных данных, который обрабатывает все методы для всех таблиц.
- Одним из преимуществ использования шлюза табличных данных для инкапсуляции доступа к базе данных является то, что один и тот же интерфейс может работать как для использования SQL запросов для работы с БД, так и для использования хранимых процедур.
Пример:
UML диаграмма будет выглядеть так:
Создаем POCO объект
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public int Age { get; set; }
}
Создаем класс PersonGateway, который содержит весь SQL для доступа к одной таблице или представлению: выбирает, вставляет, обновляет и удаляет. Другой код вызывает свои методы для всего взаимодействия с базой данных.
public class PersonGateWay
{
public void Update(string firstName, string lastName, string age)
{
// Обновляем person энтити.
}
public void Insert(string firstName, string lastName, string gender, string age)
{
// Вставляем person энтити.
}
public void Delete(int id)
{
// удаляем запись person
}
/* .... */
}
Data Mapper
Объектные и реляционные БД используют разные способы структурирования данных. Множество составляющих объектов, например коллекции и наследование, не представлены в реляционных БД. Когда проектируется объектная модель с большим количеством бизнес-логики, полезно применять такие механизмы для улучшения организации хранения данных и логики, которая работает c ними. Это приводит к различиям в организации. Так что объектная и реляционная схемы не идентичны.
Тем не менее, необходимость в обмене данными между двумя схемами не отпадает, и этот обмен становится, в свою очередь, сложным. Если же объект знает о реляционной структуре — изменения в одной из структур приведёт к проблемам в другой.
Data Mapper — это программная прослойка, разделяющая объект и БД. Его обязанность — пересылать данные между ними и изолировать их друг от друга. При использовании Data Mapper'а объекты не нуждаются в знании о существовании БД. Они не нуждаются в SQL-коде, и (естественно) в информации о структуре БД. Так как Data Mapper - это разновидность паттерна Mapper, сам объект-Mapper неизвестен объекту.
пример реализации data mapper на C#
using System.Data;
using System.Data.SqlClient;
namespace DesignPatternDemos.DataMapper
{
public class CustomerDataMapper
{
private const string CONNECTION_STRING =
"Data Source=(local);Initial Catalog=DesignPatterns;Integrated Security=True";
public static Customer GetByID(int id)
{
using(SqlConnection connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
using(SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "SELECT TOP 1 * FROM [Customer] WHERE [ID] = @ID";
command.Parameters.AddWithValue("@ID", id);
SqlDataReader reader = command.ExecuteReader();
// If the query returned a row, create the Customer object and return it.
if(reader.HasRows)
{
reader.Read();
string name = (string)reader["Name"];
bool isPremiumMember = (bool)reader["IsPremiumMember"];
return new Customer(id, name, isPremiumMember);
}
}
}
return null;
}
// Notice that we need to pass in the Customer object (or some information from it)
// to use some of the methods in the DataMapper class.
public void Save(Customer customer)
{
// This method needs to handle INSERT (new Customer) and UPDATE (existing Customer).
// Or, you would need to create two separate functions, and call them when appropriate.
// Pretend there is code here to do the insert and/or update to the database.
}
// We also could have only passed in the ID for this method,
// because that is the only value from the Customer object that this method needs.
public void Delete(Customer customer)
{
using(SqlConnection connection = new SqlConnection(CONNECTION_STRING))
{
connection.Open();
using(SqlCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "DELETE FROM [Customer] WHERE [ID] = @ID";
command.Parameters.AddWithValue("@ID", customer.ID);
command.ExecuteNonQuery();
}
}
}
}
}