300 likes | 454 Views
ORM Паттерны. Repository. Repository (хранилище) ― выступает в роли посредника между слоем домена и слоем отображения данных, предоставляя интерфейс в виде коллекции для доступа к объектам домена. Пример. Обратимся к упомянутому ранее примеру на NHibernate.
E N D
Repository Repository(хранилище)―выступаетв ролипосредникамеждуслоемдоменаи слоемотображенияданных,предоставляя интерфейсввидеколлекциидлядоступак объектамдомена.
Пример ОбратимсякупомянутомуранеепримерунаNHibernate. Пустьунасопределѐнклассавтора: publicclassAuthor { publicvirtualintId {get;set;} publicvirtualstringFirstName {get;set;} publicvirtualstringLastName {get;set;} publicvirtualintYearOfBirth {get;set;} publicvirtualIesi.Collections.Generic.ISet<Book>Books {get;set;} }
Пример Файл,которыйотображаетклассавторанатаблицуБД: <classname="Books.Domain.Author,NHibernateDemo"table="Author"> <idname="Id"type="System.Int32"> <columnname="Id"not-null="true"/> <generatorclass="identity"/> </id> <propertyname="FirstName"column="FirstName"/> <propertyname="LastName"column="LastName"/> <propertyname="YearOfBirth"type="System.Int32" column="YearOfBirth"/> <setname="Books"table="BookAuthor"> <keycolumn="AuthorId"></key> <many-to-manyclass="Books.Domain.Book,NHibernateDemo" column="BookId"></many-to-many> </set> </class>
Пример Пустьнамнеобходимозаполнитьвыпадающийсписок именамиавторов,скажем,длятогочтобы впоследствиивывестисписоквсехегоработ: publicvoidFillAuthorsComboBox(ComboBoxcomboBox,ISessionFactoryfactory) { ISessionsession=factory.OpenSession(); try { IQueryauthorsQuery=session.CreateQuery("FROMAuthor"); IList<Author>authors=authorsQuery.List<Author>(); foreach(Authorauthorinauthors) comboBox.Items.Add(author.LastName+","+author.FirstName); } finally { session.Close(); } }
Пример Пустьоднойизфункцийнашегоприложенияявляетсявывод информацииовсехавторахвформатеHTML: publicstringGetAllAuthorsAsHTML(ISessionFactoryfactory){ ISessionsession=factory.OpenSession(); try{ IQueryauthorsQuery=session.CreateQuery("FROMAuthor"); IList<Author>authors=authorsQuery.List<Author>(); StringBuilderresult=newStringBuilder(); result.Append("<HTML>").Append("<BODY>"); foreach(Authorauthorinauthors) { result.Append("<h2>").Append(author.LastName+","+author.FirstName) .Append("</h2>"); result.Append("<p>Yearofbirth:").Append(author.YearOfBirth.ToString()) .Append("</p>"); } result.Append("</BODY>").Append("</HTML>"); returnresult.ToString(); } finally{ session.Close(); } }
Пример Недостаткииспользованногоподхода: неоправданноедублирование; зависимостьотконкретнойреализации ORM; непрозрачностькода; невозможностьпротестироватькод.
Пример ОпределимклассRepository: publicclassRepository { privateISessionsession; publicRepository(ISessionsession) { this.session=session; } publicIEnumerable<Author>GetAllAuthors() { IQueryauthorsQuery=session.CreateQuery("FROMAuthor"); returnauthorsQuery.List<Author>(); } }
Пример Теперьфункции,которыеработаютсосписком авторов,можнопереписатьследующимобразом: publicvoidFillAuthorsComboBox( ComboBoxcomboBox, Repositoryrepository) { IEnumerable<Author>authors=repository.GetAllAuthors(); foreach(Authorauthorinauthors) comboBox.Items.Add( author.LastName+","+author.FirstName); }
Пример ФункцияэкспортавHTMLпреобразуетсяследующим образом: publicstringGetAllAuthorsAsHTML(Repositoryrepository) { IEnumerable<Author>authors=repository.GetAllAuthors(); StringBuilderresult=newStringBuilder(); result.Append("<HTML>").Append("<BODY>"); foreach(Authorauthorinauthors) { result.Append("<h2>") .Append(author.LastName+","+author.FirstName) .Append("</h2>"); result.Append("<p>Yearofbirth:") .Append(author.YearOfBirth.ToString()) .Append("</p>"); } result.Append("</BODY>").Append("</HTML>"); returnresult.ToString(); }
Плюсы Сокращениедублирования; прозрачностькода; возможностьсозданияфиктивного хранилищадляупрощениятестирования; скрытиедеталейреализации.
Specification Specification(спецификация)―паттерн, которыйинкапсулируетлогикуотбора доменныхобъектоввотдельныйобъект.
Specification.Пример Предположим,чтонамнеобходимоделатьвыборкиавторов, удовлетворяющиеразнымкритериям. Например,выбиратьавторов,родившихсявопределѐнныйпериод илиимякоторыхсодержитзаданноезначение: publicclassRepository{ publicIEnumerable<Author>FindAuthors_BornBetween(intstartYear,intendYear){ returnsession.QueryOver<Author>() .Where(a=> a.YearOfBirth>=startYear&& a.YearOfBirth<=endYear).List<Author>(); } publicIEnumerable<Author>FindAuthors_NameContains(stringvalue){ returnsession.QueryOver<Author>() .Where(a=> a.FirstName.Contains(value)).List<Author>(); } } ИнтерфейсклассаRepositoryможетстатьнеоправданнобольшим. Крометого,такаяреализациянарушаетпринцип открытости/закрытости.
Specification.Пример РешениемявляетсяиспользованиепаттернаСпецификация. Рассмотримпримерпримененияданногопаттерна: publicinterfaceISpecification<T> { Expression<Func<T,bool>>IsSatisfiedBy(); } publicclassRepository { publicIEnumerable<Author>FindAuthors( ISpecification<Author>specification) { returnsession.QueryOver<Author>() .Where(specification.IsSatisfiedBy()) .List<Author>(); } }
Specification.Пример Класс,реализующийинтерфейсспецификациии выполняющийпроверкудатырождениянавхождениев определѐнныйдиапазон,будетвыглядетьтак: publicclassIsYearOfBirthInRange:ISpecification<Author> { privateintendYear; privateintstartYear; publicIsYearOfBirthInRange(intstartYear,intendYear) { this.startYear=startYear; this.endYear=endYear; } publicExpression<Func<Author,bool>>IsSatisfiedBy() { returnauthor=> author.YearOfBirth>=startYear&& author.YearOfBirth<=endYear; } }
Specification.Пример Класс,реализующийинтерфейсспецификациии анализирующийимяавтора,будетследующим: publicclassAuthorNameContains:ISpecification<Author> { privatestringvalue; publicAuthorNameContains(stringvalue) { this.value=value; } publicExpression<Func<Author,bool>>IsSatisfiedBy() { returnauthor=>author.FirstName.Contains(value); } }
Specification.Пример Рассмотримтеперьпримериспользованияполученных классов. Выведемвсехавторов,родившихсявовторой половинеXXвека: publicvoidDisplayAuthors(Repositoryrepository) { IEnumerable<Author>authors= repository.FindAuthors(newIsYearOfBirthInRange(1950,2000)); foreach(Authorauthorinauthors) Console.WriteLine(author.FirstName+""+author.LastName); }
Specification Длятогочтобысделатьвыборку,удовлетворяющую несколькимусловиям,можноприменитьпаттерны компоновщикидекораторследующимобразом:
Specification Интерфейсспецификацииследуетрасширить следующимобразом: publicinterfaceISpecification<T> { Expression<Func<T,bool>>IsSatisfiedBy(); ISpecification<T>Or(ISpecification<T>left); ISpecification<T>And(ISpecification<T>left); ISpecification<T>Not(); }
Specification Класссоставногоусловиябудетследующим: publicabstractclassCompositeSpecification<T>:ISpecification<T> { publicabstractExpression<Func<T,bool>>IsSatisfiedBy(); publicISpecification<T>Or(ISpecification<T>right){ returnnewOrSpecification<T>(this,right); } publicISpecification<T>And(ISpecification<T>right){ returnnewAndSpecification<T>(this,right); } publicISpecification<T>Not(){ returnnewNotSpecification<T>(this); } }
Specification Рассмотримреализациюнаследниковупомянутоговышекласса. КлассAndSpecificationбудетследующим: publicclassAndSpecification<T>:CompositeSpecification<T> { privateISpecification<T>left; privateISpecification<T>right; publicAndSpecification(ISpecification<T>left,ISpecification<T>right) { this.left=left; this.right=right; } publicoverrideExpression<Func<T,bool>>IsSatisfiedBy() { returnExpression.Lambda<Func<T,bool>>( Expression.And( left.IsSatisfiedBy(), right.IsSatisfiedBy())); } }
Specification КлассOrSpecification: publicclassOrSpecification<T>:CompositeSpecification<T> { privateISpecification<T>left; privateISpecification<T>right; publicOrSpecification(ISpecification<T>left,ISpecification<T>right) { this.left=left; this.right=right; } publicoverrideExpression<Func<T,bool>>IsSatisfiedBy() { returnExpression.Lambda<Func<T,bool>>( Expression.Or( left.IsSatisfiedBy(), right.IsSatisfiedBy())); } }
Specification КлассNotSpecification: publicclassNotSpecification<T>:CompositeSpecification<T> { privateISpecification<T>wrapped; publicNotSpecification(ISpecification<T>wrapped) { this.wrapped=wrapped; } publicoverrideExpression<Func<T,bool>>IsSatisfiedBy() { returnExpression.Lambda<Func<T,bool>>( Expression.Not(wrapped.IsSatisfiedBy())); } }
Specification Теперьклассыконкретныхусловийдолжны наследоватьклассCompositeSpecification: publicclassIsYearOfBirthInRange:CompositeSpecification<Author> { privateintendYear; privateintstartYear; publicIsYearOfBirthInRange(intstartYear,intendYear) { this.startYear=startYear; this.endYear=endYear; } publicoverrideExpression<Func<Author,bool>>IsSatisfiedBy() { returnauthor=> author.YearOfBirth>=startYear&& author.YearOfBirth<=endYear; } }
Specification Рассмотримпримериспользования.Выберемвсех авторов,которыеродилисьневпервойполовинеXX векаиимякоторыхсодержитбукву«А»: publicstaticvoidDisplayAuthors(Repositoryrepository) { ISpecification<Author>condition= ( (newIsYearOfBirthInRange(1950,1999)).Not() ).And ( newAuthorNameContains("A") ); IEnumerable<Author>authors=repository.FindAuthors(condition); foreach(Authorauthorinauthors) Console.WriteLine(author.FirstName+""+author.LastName); }
Specification ТакаявозможностьязыкаC#3.0,какметоды расширенияпозволяетреализоватьподобную функциональностьследующимобразом: publicstaticclassSpecificationUtils { publicstaticISpecification<T>Or<T>(thisISpecification<T>left, ISpecification<T>right) { returnnewOrSpecification<T>(left,right); } publicstaticISpecification<T>And<T>(thisISpecification<T>left, ISpecification<T>right) { returnnewAndSpecification<T>(left,right); } publicstaticISpecification<T>Not<T>(thisISpecification<T>wrapped) { returnnewNotSpecification<T>(wrapped); } }
Specification КлассAndSpecificationбудетвыглядетьтак: publicclassAndSpecification<T>:ISpecification<T> { privateISpecification<T>left; privateISpecification<T>right; publicAndSpecification(ISpecification<T>left,ISpecification<T>right){ this.left=left; this.right=right; } publicExpression<Func<T,bool>>IsSatisfiedBy(){ returnExpression.Lambda<Func<T,bool>>( Expression.And( left.IsSatisfiedBy(), right.IsSatisfiedBy())); } } Остальныеподобныеклассыреализуютсяаналогично.
Specification Клиентскийкодостаѐтсябезизменений: publicstaticvoidDisplayAuthors(Repositoryrepository) { ISpecification<Author>condition= ( (newIsYearOfBirthInRange(1950,1999)).Not() ).And ( newAuthorNameContains("A") ); IEnumerable<Author>authors=repository.FindAuthors(condition); foreach(Authorauthorinauthors) Console.WriteLine(author.FirstName+""+author.LastName); }