@muyanfeixiang
2016-03-16T05:55:27.000000Z
字数 4470
阅读 2341
未分类
规约模式允许我们将一小块领域知识封装到一个单元中,即规约,然后可以在code base中对其进行复用。
它可以用来解决在查询中泛滥着GetBySomething方法的问题,以及对查询条件的组合和复用。
举个例子
public class Movie : Entity{public string Name { get; }public DateTime ReleaseDate { get; }public MpaaRating MpaaRating { get; }public string Genre { get; }public double Rating { get; }}public enum MpaaRating{G,PG13,R}
这样如果按不同的条件来查询,就会出现如下代码
public class MovieRepository{public IReadOnlyList<Movie> GetByReleaseDate(DateTime maxReleaseDate) { }public IReadOnlyList<Movie> GetByRating(double minRating) { }public IReadOnlyList<Movie> GetByGenre(string genre) { }}
如果我们想要根据多个条件来查询情况就会变得稍微负责一点,如下
public class MovieRepository{public IReadOnlyList<Movie> Find(DateTime? maxReleaseDate = null,double minRating = 0,string genre = null){/* … */}}
而且有时,我们需要在内存中筛选数据,有时我们需要在sql中筛选数据,这样就会出现如下两种写法
//内存中筛选数据public Result BuyChildTicket(int movieId){Movie movie = _repository.GetById(movieId);if (movie.MpaaRating != MpaaRating.G)return Error(“The movie is not eligible for children”);return Ok();}//数据库中筛选数据public class MovieRepository{public IReadOnlyList<Movie> FindMoviesForChildren(){return db.Where(x => x.MpaaRating == MpaaRating.G).ToList();}}
这样子就违反了DRY原则,因为领域相关的信息(儿童电影)就出现在了两个位置。此时,我们可以通过规约模式来解决该问题。我们引入一个新的类来去甄别不同类型的电影。
这样不仅移除了重复的领域信息,还可以组合多个规约。
规约模式可以在如下三个场景使用
首先给出最原始的实现方式。这种方式依赖于c#的expression。如下(这也是目前我在项目直接想到使用的方式)
// Controllerpublic void SomeMethod(){Expression<Func<Movie, bool>> expression = m => m.MpaaRating == MpaaRating.G;bool isOk = expression.Compile().Invoke(movie); // Exercising a single movievar movies = _repository.Find(expression); // Getting a list of movies//如上可以直接简写为如下一句代码movies = _repository.Find(m => m.MpaaRating == MpaaRating.G);}// Repositorypublic IReadOnlyList<Movie> Find(Expression<Func<Movie, bool>> expression){return db.Where(expression).ToList();}
这种方式,解决了GetBySomething的情况,但是领域信息是不能复用的。m => m.MpaaRating == MpaaRating.G这样的代码可能会在应用中被复制多次。
一种改进的实现方式,是使用泛型规约类,如下。
public class GenericSpecification<T>{public Expression<Func<T, bool>> Expression { get; }public GenericSpecification(Expression<Func<T, bool>> expression){Expression = expression;}public bool IsSatisfiedBy(T entity){return Expression.Compile().Invoke(entity);}}public void SomeMethod(){var specification = new GenericSpecification<Movie>(m => m.MpaaRating == MpaaRating.G);bool isOk = specification.IsSatisfiedBy(movie);var movies = _repository.Find(specification);}public IReadOnlyList<Movie> Find(GenericSpecification<Movie> specification){return db.Where(specification.Expression).ToList();}
这里呢,问题依然如上,仍旧没解决m => m.MpaaRating == MpaaRating.G不方便复用。
这种方式,我们将领域信息硬编码进规约内,外部不能或者不太可能改变。
public abstract class Specification<T>{public abstract Expression<Func<T, bool>> ToExpression();public bool IsSatisfiedBy(T entity){Func<T, bool> predicate = ToExpression().Compile();return predicate(entity);}}public class MpaaRatingAtMostSpecification : Specification<Movie>{private readonly MpaaRating _rating;public MpaaRatingAtMostSpecification(MpaaRating rating){_rating = rating;}public override Expression<Func<Movie, bool>> ToExpression(){return movie => movie.MpaaRating <= _rating;}}
这样我们使得领域信息更容易复用,而且在创建其他规约时,也不需要重复原来的规约。而且非常方便对规约进行组合,如And、Or和Not。如下
public abstract class Specification<T>{public Specification<T> And(Specification<T> specification){return new AndSpecification<T>(this, specification);}// And also Or and Not methods}public class AndSpecification<T> : Specification<T>{private readonly Specification<T> _left;private readonly Specification<T> _right;public AndSpecification(Specification<T> left, Specification<T> right){_right = right;_left = left;}public override Expression<Func<T, bool>> ToExpression(){Expression<Func<T, bool>> leftExpression = _left.ToExpression();Expression<Func<T, bool>> rightExpression = _right.ToExpression();BinaryExpression andExpression = Expression.AndAlso(leftExpression.Body, rightExpression.Body);return Expression.Lambda<Func<T, bool>>(andExpression, leftExpression.Parameters.Single());}}//如下扩展方法,是用来处理AndAlso中的表达式树参数的public static class ExpressionExt{static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1,Expression<Func<T, bool>> expr2){// need to detect whether they use the same// parameter instance; if not, they need fixingParameterExpression param = expr1.Parameters[0];if (ReferenceEquals(param, expr2.Parameters[0])){// simple versionreturn Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), param);}// otherwise, keep expr1 "as is" and invoke expr2return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body,Expression.Invoke(expr2, param)), param);}}
这样,我们就可以组合规约了,如下
var gRating = new MpaaRatingAtMostSpecification(MpaaRating.G);var goodMovie = new GoodMovieSpecification();var repository = new MovieRepository();