orm Паттерны
DESCRIPTION
ORM Паттерны. Repository. Repository (хранилище) ― выступает в роли посредника между слоем домена и слоем отображения данных, предоставляя интерфейс в виде коллекции для доступа к объектам домена. Пример. Обратимся к упомянутому ранее примеру на NHibernate. - PowerPoint PPT PresentationTRANSCRIPT
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);
}