• ABP框架


    文档目录

    本节内容:

    简介

    规约模式是一个特别的软件设计模式,业务逻辑可以使用boolean逻辑重新链接业务逻辑(维基百科).

    实践中的大部分情况,它是为实体或其它业务对象,定义可复用的过滤器.

    示例

    在此小节,我们将看到规约模式的必要性,这节是通用的,与ABP的实现无关.

    假设你有一个服务方法用来计算客户的总数,如:

    public class CustomerManager
    {
        public int GetCustomerCount()
        {
            //TODO...
            return 0;
        }
    }

     你可能想通过一个过滤器获取客户数,例如:你有优质的客户(余额超过$100,000)或根据注册年份过滤客户,于是你可以创建其它方法,如:GetPremiumCustomerCount(),GetCustomerCountRegisteredInYear(int year),GetPremiumCustomerCountRegisterdInYear(int year)等等,当你有更多条件时,不可能为每种情况创建一个组合.

    这个问题的一个解决方法是规约模式,我们可以创建一个单独的方法,获取一个参数作为过滤器:

    public class CustomerManager
    {
        private readonly IRepository<Customer> _customerRepository;
    
        public CustomerManager(IRepository<Customer> customerRepository)
        {
            _customerRepository = customerRepository;
        }
    
        public int GetCustomerCount(ISpecification<Customer> spec)
        {
            var customers = _customerRepository.GetAllList();
    
            var customerCount = 0;
            
            foreach (var customer in customers)
            {
                if (spec.IsSatisfiedBy(customer))
                {
                    customerCount++;
                }
            }
    
            return customerCount;
        }
    }

    因此,我们可以接受实现了 ISpecification<Customer> 接口的任何对象作为参数,如:

    public interface ISpecification<T>
    {
        bool IsSatisfiedBy(T obj);
    }

    然后我们可以为一个customer调用 IsSatisfiedBy 来检查这个customer是否符合意图.因此,我们可用不同的过滤器调用相同的GetCustomerCount方法,不需要修改方法本身.

    理论上这个方案很好了,但在C#中可以改进得更完美, 例如:从数据库中获取所有的客户来检查是否满足给定的规约/条件,这可不是很有效率,接下来的小节里,我们将看到ABP的实现,它解决了这个问题.

    创建规约类

    ABP定义了如下的ISpecification接口:

    public interface ISpecification<T>
    {
        bool IsSatisfiedBy(T obj);
    
        Expression<Func<T, bool>> ToExpression();
    } 

    添加一个  ToExpression() 方法,它返回一个表达式(expression),用来更好地结合IQueryable和表达式树,因此,为数据库层面接受一个过滤器,我们只需要简单地传递一个规约到一个仓储里即可.

    我们通常从 Specification<T> 类继承,而不是直接实现ISpecification<T>接口.Specification类自动实现IsSatisfiedBy方法,所以,我们只需要定义ToExpression,让我们创建些规约类:

    //Customers with $100,000+ balance are assumed as PREMIUM customers.
    public class PremiumCustomerSpecification : Specification<Customer>
    {
        public override Expression<Func<Customer, bool>> ToExpression()
        {
            return (customer) => (customer.Balance >= 100000);
        }
    }
    
    //A parametric specification example.
    public class CustomerRegistrationYearSpecification : Specification<Customer>
    {
        public int Year { get; }
    
        public CustomerRegistrationYearSpecification(int year)
        {
            Year = year;
        }
    
        public override Expression<Func<Customer, bool>> ToExpression()
        {
            return (customer) => (customer.CreationYear == Year);
        }
    }

     如你所见,我们只是实现简单的lambda表达式来定义规约,让我们用这些规约获取客户数:

    count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
    count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification(2017));

    在仓储里使用规约

    现在,我们可以优化 CustomerManager 在数据库中接受过滤器:

    public class CustomerManager
    {
        private readonly IRepository<Customer> _customerRepository;
    
        public CustomerManager(IRepository<Customer> customerRepository)
        {
            _customerRepository = customerRepository;
        }
    
        public int GetCustomerCount(ISpecification<Customer> spec)
        {
            return _customerRepository.Count(spec.ToExpression());
        }
    }

    就这么简单,我们可以传递任何规约给仓储,因此仓储可以使用表达式作为过滤器.在本例里,CustomerManager不是必要的,因此我们可以直接使用仓储和规约来查询数据库,但考虑一下:我们想在某客户上执行一个业务操作,我们可以使用规约和一个领域服务来指定customers,从而继续工作.

    组合规约

    有个强大的功能:可以用And,Or,Not 和 AndNot 扩展方法,组合规约.如:

    var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification().And(new CustomerRegistrationYearSpecification(2017)));

    我们甚至可以在已有的规约的基础上,创建一个新规约:

    public class NewPremiumCustomersSpecification : AndSpecification<Customer>
    {
        public NewPremiumCustomersSpecification() 
            : base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification(2017))
        {
        }
    }

    AndSpecification 是一个子类,它仅在两个规约都满足的情况下才符合条件,然后我们可以使用NewPremiumCustomersSpecification ,就像其它的规约一样:

    var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());

    讨论

    由于规约模式比C#的lambda表达式旧,所以通过拿来与表达式对比.一些开发者可能认为规约模式不再需要,我们可以直接传递表达式给一个仓储或领域服务,如:

    var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == 2017);

    由于ABP的仓储支持表达式,这是一个合法的用法,在应用里,你不是一定要定义或使用任何规约,于是你可以继承使用表达式,所以,规约的要点在哪?为何,何时应当考虑使用它们?

    何时使用

    使用规约的好处:

    • 可复用:设想你需要在你代码里多处用到"优质客户"过滤,如果你使用表达式而不创建一个规约,那如果在以后你需要修改"优质客户"的定义会发生什么(如:你想改成余额至少从$100,000到250,000并包含其它条件,诸如客户注册超过3年),如果你使用规约,你只需要修改一个类,如果你使用(复制/粘贴)相同的表达式,你就要修改所有用到的地方.
    • 可组合:你可以联合多个规约来创建新规约,这是另一种形式的复用.
    • 命名化:PremiumCustomerSpecification比一个复杂的表达式更好地表述了意图, 所以,如果你想一个表达式在你的业务里变得可顾名思义,那么考虑使用规约.
    • 可测试:一个规约是一个单独的(且易于)可测试的对象.

    何时不用

    • 没有业务表达式: 你可以在你的业务不涉及表达式和操作时不使用规约.
    • 创建报表:如果你只是创建一个报表就不要用规约,而直接使用IQueryable,实际上,你甚至可以使用原始的SQL,视图或其它报表工具.DDD不关心报表和底层数据库存储获取查询好处及视图性能.

    英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Specifications

  • 相关阅读:
    【死磕Java并发】—–J.U.C之AQS(一篇就够了)
    Java并发包基石-AQS详解
    java并发api总结
    用Java对CSV文件进行读写操作
    多线程批量检测未注册域名
    RSA公钥、私钥、签名和验签
    ASCII,Unicode和UTF-8终于找到一个能完全搞清楚的文章了
    Java 常用工具类---- 各种字符集编码判断与转换
    JavaMail| JavaMail配置属性
    QT下的几种透明效果(QPalette背景白色,窗口设置setWindowOpacity,QPainter使用Clear模式绘图)
  • 原文地址:https://www.cnblogs.com/kid1412/p/6404019.html
Copyright © 2020-2023  润新知