• 规约模式Specification Pattern


    什么是规约模式

    规约模式允许我们将一小块领域知识封装到一个单元中,即规约,然后可以在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。如下(这也是目前我在项目直接想到使用的方式)

    // Controller
    public void SomeMethod()
    {
        Expression<Func<Movie, bool>> expression = m => m.MpaaRating == MpaaRating.G;
        bool isOk = expression.Compile().Invoke(movie); // Exercising a single movie
        var movies = _repository.Find(expression); // Getting a list of movies
        //如上可以直接简写为如下一句代码
        movies = _repository.Find(m => m.MpaaRating == MpaaRating.G);
    }
     
    // Repository
    public 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 fixing
        ParameterExpression param = expr1.Parameters[0];
        if (ReferenceEquals(param, expr2.Parameters[0]))
        {
            // simple version
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(expr1.Body, expr2.Body), param);
        }
        // otherwise, keep expr1 "as is" and invoke expr2
        return 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();
    IReadOnlyList<Movie> movies = repository.Find(gRating.And(goodMovie));
    

    参考博客来源http://enterprisecraftsmanship.com/2016/02/08/specification-pattern-c-implementation/

  • 相关阅读:
    [Swift]LeetCode823. 带因子的二叉树 | Binary Trees With Factors
    [Swift]LeetCode822. 翻转卡片游戏 | Card Flipping Game
    [Swift]LeetCode821. 字符的最短距离 | Shortest Distance to a Character
    [Swift]LeetCode818. 赛车 | Race Car
    [Swift]LeetCode817. 链表组件 | Linked List Components
    [Swift]LeetCode816. 模糊坐标 | Ambiguous Coordinates
    [Swift]LeetCode815. 公交路线 | Bus Routes
    [Swift]LeetCode814. 二叉树剪枝 | Binary Tree Pruning
    [Objective-C语言教程]指针(15)
    转 : net use的使用
  • 原文地址:https://www.cnblogs.com/yanbinliu/p/5283307.html
Copyright © 2020-2023  润新知