zoukankan      html  css  js  c++  java
  • ABP官方文档翻译 3.5 规约

    规约

    介绍

      规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则。(维基百科)。

      尤其是,它通常用来为实体或其他业务对象定义可复用的过滤器。

    示例

      在这个部分,我们将看到规约模式的必要性。本部分是通用的,和ABP的实现没有必然的关系。

      假定,有一个服务方法,计算所有客户的总数量,如下所示:

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

      你或许希望通过过滤器获取客户数量。例如,你有优质客户(拥有超过100,000美元的客户)或者想通过注册年份过滤客户。然后你创建了其他方法,如GetPremiumCustomerCount(),GetCustomerCountRegisteredYear(int year),GetPremiumCustomerCountRegisteredInYeaar(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);
    }

      我们可以调用IsSatisfiedBy方法来测试客户是否是有意向的。从而,我们可以使用不同的参数调用同样的方法GetCustomerCount,而不用改变方法本身。

      因为这个解决方案在理论上相当好,所以在C#中它应该被改善的更好。例如,从数据库里获取所有的客户并检查他们是否满足指定的规约/条件,这个操作是非常低效的。在下一部分,我们将看到ABP如何实现这个模式并克服了这个问题。

    创建规范类

      ABP按如下方式 定义了ISpecification接口:

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

      添加了ToExpression()方法,这个方法返回一个表达式,这样可以 更好的和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);
        }
    }

      如你所见,我们仅仅实现了简单的拉姆达表达式来定义规约。让我们使用这些规约获取客户的数量:

    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是不需要的,因为我们可以直接在仓储里使用规约查询数据库。但是,我们想在一些客户上执行业务操作,在这种情况下,我们可以在领域服务里使用规约指定需要操作的客户。

    组合规约

      规约一个强大的特征是可以使用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是Specification类的一个子类,这个类只有两个规约都满足时才满足。因此我们可以像其他规约那样使用NewPremiumCustomersSpecification:

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

    讨论

      因为规约模式比C#拉姆达表达式久远,它经常和表达式比较。一些开发者可能认为不再需要规约模式,我们可以直接传递表达式给仓储或领域服务,如下:

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

      因为ABP仓储支持表达式,所以这种使用方式完全有效。你不需要在应用里定义或使用任何规约,你可以继续使用表达式。所以,规约的点是什么?为什么还有什么时候我们该考虑使用它呢?

    什么时候使用?

      使用规约的一些好处:

    • 可复用:设想你在代码的很多地方都需要使用PremiumCustomer过滤器。如果你使用表达式而不是创建规约,如果以后会更改“Premium Customer”的定义(比如,你想要更改优质的标准从$100000到$250000并且添加另一个条件如客户必须大于3)将会放生什么呢。如果你使用规约,仅仅需要更改一个类。如果你使用(复制/粘贴)同样的表达式,需要全部更改他们。
    • 可组合:你可以组合多个规约创建一个新的规约。这是另一种类型的复用。
    • 命名的:PremiumCustomerSpecification比使用复杂的表达式更能清晰的表达意图。所以,如果在业务中这个表达式是有意义的,考虑使用规约。
    • 可测试:规约是独立易测试的对象。

    什么时候不使用? 

    • 没有业务表达式:你可以考虑不使用规约,如果表达式、操作没有业务的话。
    • 报表:如果你仅仅创建一个报表,就不要创建规约,直接使用IQueryable。实际上,你甚至可以使用平常的SQL、师徒和其他报表工具。DDD对报表不怎么关心,从性能角度来讲,可以使用数据存储查询的好处是非常重要的。

    返回主目录

  • 相关阅读:
    Centos下安装Spark
    Centos下安装Scala(2)
    Spark寒假实验1
    Mybatis 学习记录 续
    Centos下安装Scala(1)
    putty【cmd命令】
    cmd查看命令所在【全路径】
    linux开关机相关
    Xml WebService完全实例解析(一)
    Xml WebService完全实例解析(二)
  • 原文地址:https://www.cnblogs.com/xajh/p/6858976.html
Copyright © 2011-2022 走看看