走向ASP.NET架构设计第五章:业务层模式,原则,实践(前篇)

  在上一章中,我们讲述了有关业务层分层的一些知识,下面我们就来看看,在具体的业务层的设计中,我们可以采用哪些模式可以将业务层设计的更加的灵活!

  架构模式

  首先我们就来看看,如何更加有效的组织业务规则。

  Specification Pattern(需求规格模式)

  这个模式的使用方法就是:把业务规则放在业务类的外面,并且封装成为一个个返回boolean值的算法。这些一个个的业务规则的算法不仅仅便于管理和维护,并且还可以被重用,而且很方便的组织成为复杂的业务逻辑。

  下面我们就来看一个以在线租DVD的公司的例子。例子很简单,场景也很简单:判断一个用户是否可以租更多的DVD。下面就是我们设计的一个基本的类图。(大家肯定觉得一上来就看类图有点突兀,没有一步步的分析,其实我是想让大家知道,所讲的是个什么东西样子,之后大家再慢慢的理解)

  下面我们就开始做这个事情:

  1、创建一个新的解决方案,命名为:ASPPatterns.Chap5.Specification。

  2、然后添加一个C#的类库:ASPPatterns.Chap5. Specification.Model。

  3、在这个Model的类库中添加一个接口:ISpecification。

public interface ISpecification<T>

{

bool IsSatisfiedBy(T candidate);

}

  上面的代码,其实就是把一个个的业务规则抽象出来了。我们知道,在系统中,不管业务规则多么复杂,最后在进行业务逻辑判定的时候,最后的结果还是“是否通过”。所以在这里就进行了抽象。

  因为我们的例子是以一个在线租赁DVD为例子,用户可以来租赁DVD,其中也是有一定的规则的,例如,如果用户已经租了5盘DVD,那么我们就会考虑,这个用户时候还可以继续租DVD。至于根据什么判断:可能DVD公司规定一个人最多不能超过5盘,或者DVD公司认为某个用户的信誉不好等等。

  下面我们就来定义个具体的业务规则:HasReachedRentalThresholdSpecification

  根据这个规则就决定一个用户是否可以租DVD。   

public class HasReachedRentalThresholdSpecification : ISpecification<CustomerAccount>

{

public override bool IsSatisfiedBy(CustomerAccount candidate)

{

return candidate.NumberOfRentalsThisMonth >= 5;

}

}

  这个规则定义出来后,我们就在业务类中使用这个规则:

public class CustomerAccount

{

private ISpecification<CustomerAccount> _hasReachedRentalThreshold;

public CustomerAccount()

{

_hasReachedRentalThreshold = new HasReachedRentalThresholdSpecification(); }

public decimal NumberOfRentalsThisMonth { get; set; }

public bool CanRent()

{

return !_hasReachedRentalThreshold.IsSatisfiedBy(this);

}

}

  当然,我们可以把更多的业务规则组合进来。

  这个例子到这里就完了,这个例子中只是简单的采用了Specifiction模式。但是实际的情况往往是没有这个简单的,因为一个业务逻辑往往要组合多个多个业务规则。下面我们就来进一步的看:如果采用链式的结构来完成复杂的业务逻辑。

  Composite Pattern(组合模式)

  注:这个模式不属于架构模式,而且GOF模式的一种,这里列出来主要是为了配合之前的Specification模式的,大家不要在这里纠结这个问题。

  Composite模式允许把一个集合对象当做单个的对象来使用,而且我们还可以在这个所谓的”单个对象”中不断的嵌套。采用这种模式,可以把对象的层级关系组合成为“树形”的结构!我个人喜欢把它称为“容器模式”。

  其实这个模式在我们在平时的ASP.NET或者WinForm ,WPF中到处可见。例如一个Panel控件,可以在里面加入另一个Panel,然后在Panel中可以加入GroupBox,然后再GroupBox中还可以加入Button等控件。这就是.NET Framework设计中采用了Compiste模式的例子。

  下面来看看Compiste模式的UML结构图:

  在上面的图中:

  1. Component是一个抽象类,这个类提供了一个Add方法,这个Add可以加入其他的Component.大家想想,这样是否就可以很容易的实现链式的效果。

  2. Leaf就是一个继承Component的具体类。

  看到上面图,其实大家也可以想想在ASP.NET页面的生命周期中到处都是这种例子:例如在ASP.NET页面的Init事件中,因为Page本身就是一个容器,这个容器里面包含了很多的其他的控件,如Panel,Button,而且Panel里面还是控件。那么在Init方法就会调用自己的子容器的Init方法,然后子容器在调用自己的子容器的Init方法,这样就层层调用,直到最后调用到某个控件的Init的方法。这样这个页面的初始化就完成了。和上面的UML的结构是一样的。

  下面我们还是来看一个例子吧。继续之前的Specification模式的讨论,看看如果结合则两种模式来组织复杂的业务逻辑。

  为了使得例子有点说服力,我们把之前的业务稍微的变复杂一点点:为了判定一个用户是否可以租DVD,我们要进行一系列的规则判定之后才能决定结果:

  1、用户的账号是否处于激活的状态。

  2、用户之前是否还欠费。

  3、用户租赁DVD的数量是否达到了规定的数量。

  下面首先总体来看看一些类图的结构:

  不知道大家有没有注意一点:每次我在讲述一个功能的时候,总是先让大家看看总体的类图的设计,然后再开始一个个的讲述。其实这样做事有原因的。在之前的文章中,一直提到“设计Design”。就是说在做一个功能之前,不是一下子就砸进去编码,而是首先把功能考虑清楚,然后从总体上考虑功能如何实现,然后写出一些测试代码,最后写出一些实现代码的骨架。上面的类图其实就是一个骨架。

  骨架出来了,下面就继续开始实现,首先,因为要考虑到用户有了”是否处于激活状态”,那么就在之前的CustomerAccoutn中加入属性AccountActive.而且还要加入另外的属性LateFees来保存用户的欠费的多少。

public class CustomerAccount

{

private ISpecification<CustomerAccount> _hasReachedRentalThreshold;

public CustomerAccount()

{

_hasReachedRentalThreshold = new HasReachedRentalThresholdSpecification(); }

public decimal NumberOfRentalsThisMonth { get; set; }

public bool AccountActive { get; set; }

public decimal LateFees { get; set; }

public bool CanRent()

{

return !_hasReachedRentalThreshold.IsSatisfiedBy(this);

}

}

  那么随着这个需求的变化,之前的CanRent方法也要改变了。

  按照之前的Specification模式的例子,我们首先条件两个类来新增的封装业务规则:

public class CustomerAccountStillActiveSpecification : ISpecification<CustomerAccount>

{

public override bool IsSatisfiedBy(CustomerAccount candidate)

{

return candidate.AccountActive;

}

}

  上面的代码用来判断用户是否处于激活状态:

public class CustomerAccountHasLateFeesSpecification : ISpecification<CustomerAccount>

{

public override bool IsSatisfiedBy(CustomerAccount candidate)

{

return candidate.LateFees > 0;

}

}

  上面的代码就判断用户是否欠费,添加完了所有的业务规则之后,好戏就开始了。我们要把这些业务规则组合起来,放在容器中,然后只要调用父容器的一个方法,规则验证就一层层进行下去,就像我们之前举的ASP.NET的Init事件一样。

  首先我们来添加一个表示容器的类:

public abstract class CompositeSpecification<T> : ISpecification<T>

{

public abstract bool IsSatisfiedBy(T candidate);

public ISpecification<T> And(ISpecification<T> other)

{

return new AndSpecification<T>(this, other);

}

public ISpecification<T> Not()

{

return new NotSpecification<T>(this);

}

}

  上面的代码有些不明白的地方,没什么,咱们耐心的往下面走。

public class AndSpecification<T> : CompositeSpecification<T>

{

private ISpecification<T> _leftSpecification;

private ISpecification<T> _rightSpecification;

public AndSpecification(ISpecification<T> leftSpecification, ISpecification<T> rightSpecification)

{

_leftSpecification = leftSpecification;

_rightSpecification = rightSpecification;

}

public override bool IsSatisfiedBy(T candidate)

{

return _leftSpecification.IsSatisfiedBy(candidate) && _rightSpecification.IsSatisfiedBy(candidate);

}

}

public class NotSpecification<T> : CompositeSpecification<T>

{

private ISpecification<T> _innerSpecification;

public NotSpecification(ISpecification<T> innerSpecification)

{

_innerSpecification = innerSpecification;

}

public override bool IsSatisfiedBy(T candidate)

{

return !_innerSpecification.IsSatisfiedBy(candidate);

}

}

  上面基础代码完成了,我们就开始实现我们想要的链式的效果!我们修改之前的几个规则,和接口的定义,如下:

public class HasReachedRentalThresholdSpecification :CompositeSpecification<CustomerAccount>

{



}

public class CustomerAccountStillActiveSpecification :CompositeSpecification<CustomerAccount>

{



}

public class CustomerAccountHasLateFeesSpecification :CompositeSpecification<CustomerAccount>

{



}

  漫长的过程终于结束了,到了核心的部分,请看业务类现在的定义:

public class CustomerAccount

{

private ISpecification<CustomerAccount> _hasReachedRentalThreshold;

private ISpecification<CustomerAccount> _customerAccountIsActive;

private ISpecification<CustomerAccount> _customerAccountHasLateFees;

public CustomerAccount()

{

_hasReachedRentalThreshold = new HasReachedRentalThresholdSpecification();

_customerAccountIsActive = new CustomerAccountStillActiveSpecification();

_customerAccountHasLateFees = new CustomerAccountHasLateFeesSpecification();

}

public decimal NumberOfRentalsThisMonth { get; set; }

public bool AccountActive { get; set; }

public decimal LateFees { get; set; }

public bool CanRent()

{

ISpecification<CustomerAccount> canRent = _customerAccountIsActive.And(_hasReachedRentalThreshold.Not()).And(_customerAccountHasLateFees.Not());

return canRent.IsSatisfiedBy(this);

}

}

  大家主要看看那个 CanRent方法,下面我们就来讲讲这个方法。customerAccountActive继承自CompositeSpecification,而Add方法的定义如下:

public ISpecification<T> And(ISpecification<T> other)

{

return new AndSpecification<T>(this, other);

}

  _customerAccountIsActive.And(_hasReachedRentalThreshold.Not())的结果就是使得customerAccountIsActive内部包含了平行的两条业务规则,结构如下:

  方法返回的结果还是一个实现了ISpecification的对象,只不过这个对象(我们称之为“容器A”)里面有两个规则了。

  然后这个保量两个业务规则的对象(容器A)再次调用Add方法,如下:

_customerAccountIsActive.And(_hasReachedRentalThreshold.Not()).And(_customerAccountHasLateFees.Not());

  此时相当于把之前那个容器A作为一个单独对象,再次调用Add方法,于是这个三个规则组合成为一个大的规则的容器:如下。

  今天就到这里,东西不多,大家多琢磨一下!

时间: 2024-09-18 21:21:55

走向ASP.NET架构设计第五章:业务层模式,原则,实践(前篇)的相关文章

一起谈.NET技术,走向ASP.NET架构设计——第四章—业务层分层架构(中篇)

在上一篇文章中,我们讨论了两种组织业务逻辑的模式:Transaction Script和Active Record.在本篇中开始讲述Domain Model和Anemic Model. Domain Model 在开发过程中,我们常常用Domain Model来对目标的业务领域建模.通过Domain Model建模的业务类代表了目标领域中的一些概念.而且,我们会看到通过Domain Model建模的一些对象模拟了业务活动中的数据,有的对象还反映了一些业务规则. 我们就来看看电子商务系统的开发,在

走向ASP.NET架构设计第四章业务层分层架构(中篇)

在上一篇文章中,我们讨论了两种组织业务逻辑的模式:Transaction Script和Active Record.在本篇中开始讲述Domain Model和Anemic Model. Domain Model 在开发过程中,我们常常用Domain Model来对目标的业务领域建模.通过Domain Model建模的业务类代表了目标领域中的一些概念.而且,我们会看到通过Domain Model建模的一些对象模拟了业务活动中的数据,有的对象还反映了一些业务规则. 我们就来看看电子商务系统的开发,在

一起谈.NET技术,走向ASP.NET架构设计——第五章:业务层模式,原则,实践(前篇)

在上一章中,我们讲述了有关业务层分层的一些知识,下面我们就来看看,在具体的业务层的设计中,我们可以采用哪些模式可以将业务层设计的更加的灵活! 架构模式 首先我们就来看看,如何更加有效的组织业务规则. Specification Pattern(需求规格模式) 这个模式的使用方法就是:把业务规则放在业务类的外面,并且封装成为一个个返回boolean值的算法.这些一个个的业务规则的算法不仅仅便于管理和维护,并且还可以被重用,而且很方便的组织成为复杂的业务逻辑. 下面我们就来看一个以在线租DVD的公司

走向ASP.NET架构设计第七章:阶段总结,实践篇(中篇)

服务层(中篇) 上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计.如我们之前所讨论的:服务层想客户端暴露简单易用的API. 如下图所示: 在上图中: 1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约. 2. ASPPatterns.Chap6.EventTickets.Service:这个类库中包含了上面接口契约的实现类以及业务逻辑的协调和数据的持久化和返回数据 3. ASPPa

一起谈.NET技术,走向ASP.NET架构设计——第七章:阶段总结,实践篇(中篇)

服务层(中篇) 上一篇文章中,我们已经讲述了业务逻辑层和数据访问层层的设计和编码,下面我们就来讲述服务层的设计.如我们之前所讨论的:服务层想客户端暴露简单易用的API. 如下图所示: 在上图中: 1. ASPPatterns.Chap6.EventTickets.Contract: 这个类库中定义了服务层的接口契约. 2. ASPPatterns.Chap6.EventTickets.Service:这个类库中包含了上面接口契约的实现类以及业务逻辑的协调和数据的持久化和返回数据 3. ASPPa

走向ASP.NET架构设计第六章:服务层设计(前篇)

本篇主要是为后文做铺垫,所以理论的东西相对而言比较的多一点! 服务层的概述 首先解释一下什么是"服务Service",从广义来讲:只要是你使用了别人的东西,那么你就在使用别人提供的服务.在这里,服务就是指可能被一个或者多个系统使用的核心的业务逻辑,我们可以把服务简单的想象成为一些可供调用的API. 在之前的第四章中,我们讲述了如何组织业务逻辑,第五章讲述了在业务层的设计中可以采用的一些模式.但是还有一个问题需要大家考虑的是:如何把业务层提供给其他的层来调用? 可能认为这个问题有莫名奇妙

一起谈.NET技术,走向ASP.NET架构设计——第六章:服务层设计(前篇)

本篇主要是为后文做铺垫,所以理论的东西相对而言比较的多一点! 服务层的概述 首先解释一下什么是"服务Service",从广义来讲:只要是你使用了别人的东西,那么你就在使用别人提供的服务.在这里,服务就是指可能被一个或者多个系统使用的核心的业务逻辑,我们可以把服务简单的想象成为一些可供调用的API. 在之前的第四章中,我们讲述了如何组织业务逻辑,第五章讲述了在业务层的设计中可以采用的一些模式.但是还有一个问题需要大家考虑的是:如何把业务层提供给其他的层来调用? 可能认为这个问题有莫名奇妙

走向ASP.NET架构设计第三章:分层设计,初涉架构(前篇)

本篇主要讲述ASP.NET应用中如何进行逻辑分层.本篇的前篇会从Smart UI 反模式和它的一些缺点开始讲述,然后一步步的讲述如何逻辑分层,而且在后篇中也会给出一个ASP.NET设计中常用的仅供参考的分层架构的Demo. 一个稳定和易维护的系统必须建立在一个好的基础之上.计划和设计一个好的架构对一个项目的成败起着至关重要的作用.可能在我们一般做项目的时候,经验告诉我们:3层,N层的设计,基本就能把问题解决了,很多的情况确实是这样的.在提出一个设计的时候,常常要考虑为什么要这样划分结构,而且常常

走向ASP.NET架构设计第三章:分层设计,初涉架构(后篇)

接上篇 4.数据访问层设计 数据访问层,这块要说的不多.但是要澄清一点:数据访问不一定就是访问数据库,虽然多数的情况下,我们确实把数据存储在数据库中. 这里我们用数据库存储数据,并且用Linq To Sql来进行数据访问操作. 下面我们就来实现数据操作的一些代码: public class ProductRepository : IProductRepository { public IList<Model.Product> FindAll() { var products = from p