ORM паттерны

advertisement
ORM Паттерны
Repository
Repository (хранилище) ― выступает в
роли посредника между слоем домена и
слоем отображения данных, предоставляя
интерфейс в виде коллекции для доступа к
объектам домена.
Пример
Обратимся к упомянутому ранее примеру на NHibernate.
Пусть у нас определѐн класс автора:
public class Author
{
public virtual int Id
{ get; set; }
public virtual string FirstName
{ get; set; }
public virtual string LastName
{ get; set; }
public virtual int YearOfBirth
{ get; set; }
public virtual Iesi.Collections.Generic.ISet<Book> Books
{ get; set; }
}
Пример
Файл, который отображает класс автора на таблицу БД:
<class name="Books.Domain.Author, NHibernateDemo" table="Author">
<id name="Id" type="System.Int32" >
<column name="Id" not-null="true" />
<generator class="identity"/>
</id>
<property name="FirstName" column="FirstName"/>
<property name="LastName" column="LastName"/>
<property name="YearOfBirth" type="System.Int32"
column="YearOfBirth"/>
<set name="Books" table="BookAuthor">
<key column="AuthorId"></key>
<many-to-many class ="Books.Domain.Book, NHibernateDemo"
column="BookId"></many-to-many>
</set>
</class>
Пример
Пусть нам необходимо заполнить выпадающий список
именами авторов, скажем, для того чтобы
впоследствии вывести список всех его работ:
public void FillAuthorsComboBox(ComboBox comboBox, ISessionFactory factory)
{
ISession session = factory.OpenSession();
try
{
IQuery authorsQuery = session.CreateQuery("FROM Author");
IList<Author> authors = authorsQuery.List<Author>();
foreach (Author author in authors)
comboBox.Items.Add(author.LastName + ", " + author.FirstName);
}
finally
{
session.Close();
}
}
Пример
Пусть одной из функций нашего приложения является вывод
информации о всех авторах в формате HTML:
public string GetAllAuthorsAsHTML(ISessionFactory factory) {
ISession session = factory.OpenSession();
try {
IQuery authorsQuery = session.CreateQuery("FROM Author");
IList<Author> authors = authorsQuery.List<Author>();
StringBuilder result = new StringBuilder();
result.Append("<HTML>").Append("<BODY>");
foreach (Author author in authors)
{
result.Append("<h2>").Append(author.LastName + ", " + author.FirstName)
.Append("</h2>");
result.Append("<p>Year of birth: ").Append(author.YearOfBirth.ToString())
.Append("</p>");
}
result.Append("</BODY>").Append("</HTML>");
return result.ToString();
}
finally {
session.Close();
}
}
Пример
Недостатки использованного подхода:
 неоправданное дублирование;
зависимость от конкретной реализации
ORM;
 непрозрачность кода;
 невозможность протестировать код.

Схема
Пример
Определим класс Repository:
public class Repository
{
private ISession session;
public Repository(ISession session)
{
this.session = session;
}
public IEnumerable<Author> GetAllAuthors()
{
IQuery authorsQuery = session.CreateQuery("FROM Author");
return authorsQuery.List<Author>();
}
}
Пример
Теперь функции, которые работают со списком
авторов, можно переписать следующим образом:
public void FillAuthorsComboBox(
ComboBox comboBox,
Repository repository)
{
IEnumerable<Author> authors = repository.GetAllAuthors();
foreach (Author author in authors)
comboBox.Items.Add(
author.LastName + ", " + author.FirstName);
}
Пример
Функция экспорта в HTML преобразуется следующим
образом:
public string GetAllAuthorsAsHTML(Repository repository)
{
IEnumerable<Author> authors = repository.GetAllAuthors();
StringBuilder result = new StringBuilder();
result.Append("<HTML>").Append("<BODY>");
foreach (Author author in authors)
{
result.Append("<h2>")
.Append(author.LastName + ", " + author.FirstName)
.Append("</h2>");
result.Append("<p>Year of birth: ")
.Append(author.YearOfBirth.ToString())
.Append("</p>");
}
result.Append("</BODY>").Append("</HTML>");
return result.ToString();
}
Плюсы




Сокращение дублирования;
прозрачность кода;
возможность создания фиктивного
хранилища для упрощения тестирования;
скрытие деталей реализации.
Specification
Specification (спецификация) ― паттерн,
который инкапсулирует логику отбора
доменных объектов в отдельный объект.
Specification
Specification. Пример
Предположим, что нам необходимо делать выборки авторов,
удовлетворяющие разным критериям.
Например, выбирать авторов, родившихся в определѐнный период
или имя которых содержит заданное значение:
public class Repository {
public IEnumerable<Author> FindAuthors_BornBetween(int startYear, int endYear) {
return session.QueryOver<Author>()
.Where(a =>
a.YearOfBirth >= startYear &&
a.YearOfBirth <= endYear).List<Author>();
}
public IEnumerable<Author> FindAuthors_NameContains(string value) {
return session.QueryOver<Author>()
.Where(a =>
a.FirstName.Contains(value)).List<Author>();
}
}
Интерфейс класса Repository может стать неоправданно большим.
Кроме того, такая реализация нарушает принцип
открытости/закрытости.
Specification. Пример
Решением является использование паттерна Спецификация.
Рассмотрим пример применения данного паттерна:
public interface ISpecification<T>
{
Expression<Func<T, bool>> IsSatisfiedBy();
}
public class Repository
{
public IEnumerable<Author> FindAuthors(
ISpecification<Author> specification)
{
return session.QueryOver<Author>()
.Where(specification.IsSatisfiedBy())
.List<Author>();
}
}
Specification. Пример
Класс, реализующий интерфейс спецификации и
выполняющий проверку даты рождения на вхождение в
определѐнный диапазон, будет выглядеть так:
public class IsYearOfBirthInRange : ISpecification<Author>
{
private int endYear;
private int startYear;
public IsYearOfBirthInRange(int startYear, int endYear)
{
this.startYear = startYear;
this.endYear = endYear;
}
public Expression<Func<Author, bool>> IsSatisfiedBy()
{
return author =>
author.YearOfBirth >= startYear &&
author.YearOfBirth <= endYear;
}
}
Specification. Пример
Класс, реализующий интерфейс спецификации и
анализирующий имя автора, будет следующим:
public class AuthorNameContains : ISpecification<Author>
{
private string value;
public AuthorNameContains(string value)
{
this.value = value;
}
public Expression<Func<Author, bool>> IsSatisfiedBy()
{
return author => author.FirstName.Contains(value);
}
}
Specification. Пример
Рассмотрим теперь пример использования полученных
классов.
Выведем всех авторов, родившихся во второй
половине XX века:
public void DisplayAuthors(Repository repository)
{
IEnumerable<Author> authors =
repository.FindAuthors(new IsYearOfBirthInRange(1950, 2000));
foreach (Author author in authors)
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Specification
Для того чтобы сделать выборку, удовлетворяющую
нескольким условиям, можно применить паттерны
компоновщик и декоратор следующим образом:
Specification
Интерфейс спецификации следует расширить
следующим образом:
public interface ISpecification<T>
{
Expression<Func<T, bool>> IsSatisfiedBy();
ISpecification<T> Or(ISpecification<T> left);
ISpecification<T> And(ISpecification<T> left);
ISpecification<T> Not();
}
Specification
Класс составного условия будет следующим:
public abstract class CompositeSpecification<T> : ISpecification<T>
{
public abstract Expression<Func<T, bool>> IsSatisfiedBy();
public ISpecification<T> Or(ISpecification<T> right) {
return new OrSpecification<T>(this, right);
}
public ISpecification<T> And(ISpecification<T> right) {
return new AndSpecification<T>(this, right);
}
public ISpecification<T> Not() {
return new NotSpecification<T>(this);
}
}
Specification
Рассмотрим реализацию наследников упомянутого выше класса.
Класс AndSpecification будет следующим:
public class AndSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> left;
private ISpecification<T> right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override Expression<Func<T, bool>> IsSatisfiedBy()
{
return Expression.Lambda<Func<T, bool>>(
Expression.And(
left.IsSatisfiedBy(),
right.IsSatisfiedBy()));
}
}
Specification
Класс OrSpecification:
public class OrSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> left;
private ISpecification<T> right;
public OrSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override Expression<Func<T, bool>> IsSatisfiedBy()
{
return Expression.Lambda<Func<T, bool>>(
Expression.Or(
left.IsSatisfiedBy(),
right.IsSatisfiedBy()));
}
}
Specification
Класс NotSpecification:
public class NotSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> wrapped;
public NotSpecification(ISpecification<T> wrapped)
{
this.wrapped = wrapped;
}
public override Expression<Func<T, bool>> IsSatisfiedBy()
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(wrapped.IsSatisfiedBy()));
}
}
Specification
Теперь классы конкретных условий должны
наследовать класс CompositeSpecification:
public class IsYearOfBirthInRange : CompositeSpecification<Author>
{
private int endYear;
private int startYear;
public IsYearOfBirthInRange(int startYear, int endYear)
{
this.startYear = startYear;
this.endYear = endYear;
}
public override Expression<Func<Author, bool>> IsSatisfiedBy()
{
return author =>
author.YearOfBirth >= startYear &&
author.YearOfBirth <= endYear;
}
}
Specification
Рассмотрим пример использования. Выберем всех
авторов, которые родились не в первой половине XX
века и имя которых содержит букву «А»:
public static void DisplayAuthors(Repository repository)
{
ISpecification<Author> condition =
(
(new IsYearOfBirthInRange(1950, 1999)).Not()
).And
(
new AuthorNameContains("A")
);
IEnumerable<Author> authors = repository.FindAuthors(condition);
foreach (Author author in authors)
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Specification
Такая возможность языка C# 3.0, как методы
расширения позволяет реализовать подобную
функциональность следующим образом:
public static class SpecificationUtils
{
public static ISpecification<T> Or<T>(this ISpecification<T> left,
ISpecification<T> right)
{
return new OrSpecification<T>(left, right);
}
public static ISpecification<T> And<T>(this ISpecification<T> left,
ISpecification<T> right)
{
return new AndSpecification<T>(left, right);
}
public static ISpecification<T> Not<T>(this ISpecification<T> wrapped)
{
return new NotSpecification<T>(wrapped);
}
}
Specification
Класс AndSpecification будет выглядеть так:
public class AndSpecification<T> : ISpecification<T>
{
private ISpecification<T> left;
private ISpecification<T> right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
this.left = left;
this.right = right;
}
public Expression<Func<T, bool>> IsSatisfiedBy() {
return Expression.Lambda<Func<T, bool>>(
Expression.And(
left.IsSatisfiedBy(),
right.IsSatisfiedBy()));
}
}
Остальные подобные классы реализуются аналогично.
Specification
Клиентский код остаѐтся без изменений:
public static void DisplayAuthors(Repository repository)
{
ISpecification<Author> condition =
(
(new IsYearOfBirthInRange(1950, 1999)).Not()
).And
(
new AuthorNameContains("A")
);
IEnumerable<Author> authors = repository.FindAuthors(condition);
foreach (Author author in authors)
Console.WriteLine(author.FirstName + " " + author.LastName);
}
Download