求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
总结一下领域模型的验证
 

2011-06-14 作者:xuefly 来源:博客园

 

一:什么是领域模型(Domain Model)

1,Entities

2,Value Objects

3,Relations

二:只谈验证(Validation)——三种常见的做法

1,Constructor/Method based Validation

2,Validate() Method

3,Validation Services

4,Validation Configuration

一:什么是领域模型(Domain Model)

我们可以在概念层次认为Domain Model就是在大的领域边界中的,可以用基于离散的思想来限定出的,承载“数据”和“关系”的小边界(个人给的定义,仅供参考)。定义中蕴含着这样一层意思:所谓模型乃是我们限定出的用于解决问题的承载着“数据”与“关系”的“问题边界”,也就是Model不一定跟现实中的真实物理对象一一对应,虽然大都一一对应。领域大边界由Model小边界来明确,Model小边界需要由领域大边界来给限定出问题的范围(因为宇宙是无穷的开放的,除了那个终极规律外的任何规律都是有适用范围的,不限定出范围就会寸步难行,无法认识任何问题了)。

领域模型承载的数据可以分为两个类别:Entities和Value Objects。Model中的数据依照一定“规则”模拟出一个有机的问题模型,这个“规则”约等于Relations。

1, Entity是这样的数据,在它的生命周期中需要一个标识(Identity)。在Domain Model中(注意这里的Model是在一个限定的领域中的)如果某条数据脱离了这个标识就没有了意义的话,那么这样的数据就是该Domain Model的Entities类别的数据了。比如在cnblogs的系统中,每一个BlogSite都有一个BlogTitle,但是这个BlogTitle是可以修改的,当我修改了我的BlogSite的BlogTile后我却依然能够通过“www.cnblogs.com/xuefly”访问到,这里可以说“www.cnblogs.com/xuefly”就是我的blog的标识了。如果没有这个标识存在的话,那么BlogTitle在空间中的存在也就没有了意义(当BlogTitle与BlogSite脱离后BlogTitle就变成了“字符串”,而在Blog领域中“字符串”是没有意义的)。像BlogTitle这样的数据就是属于BlogSite模型的的Entitys类别的数据,我们把这样的数据交给BlogSite来验证的话是合理的。比如当我们修改自己的BlogSite的BlogTile中包含敏感的非法字符时或者长度超过了一定限度时,修改就不会保存成功,这里的验证就该是由BlogSite本身来完成的。

2, Value Objects是这样的数据:它的存在不需要标识。存在就是要有意义,还是在cnblogs的系统中,每个BlogSite都会有博主(BlogOwner),因为我们的BlogSite是单用户的所以在BlogSite中应该会有一个BlogUser:Person类型的属性,我们假设这个属性被命名为BlogOwner。

BlogOwner是一个BlogUser类型的对象,BlogUser类型对象的存在不依赖于BlogSite的存在,也就是即使没有BlogSite的话这个被指向了BlogSite.BlogOwner的BlogUser类型的对象依然有存在的意义(是否可以这样理解,BlogUser是一个Person,而Person在Blog领域中是具有意义的)。这里的BlogOwner就可以归为BlogSite的Value Object类别的数据了。我们看到BlogSite有一个ID标识,这个ID标识是提供给BlogTitle这样的BlogSite的Entitys类别的数据的,并不是提供给BlogOwner的。再进一步说明就是:在一个BlogSite类型的对象blogSite1中,blogSite1的BlogOwner属性值原来指向user1,而现在我们把blogSite1.BlogOwner指向null,这时user1失去了与blogSite1的联系,然而user1却在整个Blog领域中依旧具有实际的意义,因为在Blog领域中必须有用户这个概念(BlogUser Class),user1是一个用户,user1在blog领域中依旧有意义。而BlogTitle就不同了(blogSite1.BlogTitle = blogTitle1),如果我们把blogSite1.BlogTitle指向null的话,BlogTitle1同样与blogSite1脱离了关系,这个时候blogTitle1由“博客标题”变成了“字符串”,blogTitle1是个字符串,它在Blog领域中没有意义。BlogOwner属于BlogSite的Value Objects类别的数据,BlogOwner的数据应交由BlogUser模型来验证,不应由BlogSite模型来验证。与BlogSite一样BlogUser.Name,BlogUser.LoginID,BlogUser.Password属于BlogUser模型的Entitys类别的数据。

3,关系(Relations)

……

二,只谈验证(Domain Model Validation)

业务规则要求我们的Domain Model必须满足某些约束,比如BlogSite的BlogTitle的长度不能大于等于255个字符等,这就是业务规则。如果说对BlogTitle的长度进行约束貌似还有点不怎么说的通的话,那么在Blog领域中业务规则要求BlogUser类型的对象的Age属性不能小于等于零就是无可厚非的了。正是这些被约束的数据组成了Domain Model,通过这些约束,低级的数据(基本数据类型)被我们组织成了更高一级的复杂类型的数据——Domain Model Class,然后领域中的所有Domain Model交织起来最终又诠释了整个领域。我们认为:没有边界的宇宙中的每一个概念都是被约束出来的,无论是“领域”还是领域中的“模型”,终极都是由规则约束出来的具有边界的问题模型。如果没有了约束就没有了Model没有了Domain,一切可以认识的东西都没有了,只剩下了一个开放的没有边界的宇宙了。可见“规则”(Rule)是多么的重要,而执行规则就需要“验证”(Validation)。

1, 基于构造的验证

将验证放在构造对象的时候,比如构造函数中或者放在属性中。在这种情况下,当验证失败的时候我们一般直接抛出异常,比如抛出自定义的ValidationException异常,将错误信息放在自定义异常中。

public class Person : Entity
{
private string _name;
private DateTime _birthday;

public string Name
{
get
{
return _name;
}
set
{
if (value.IsNullOrEmpty())
{
throw new IsNullOrEmptyException("名称不能为空");
}
_name = value;
}
}

public DateTime Birthday
{
get
{
return _birthday;
}
set
{
if (value >= DateTime.Now || DateTime.Now.AddYears(-120) > value)
{
throw new ValidationException("出生日期不在有效的范围内");
}
_birthday = value;
}
}
}

测试如下:

[TestFixture]
public class PersonTest
{
[TestCase]
public void TestPerson()
{
Person person =
new Person { Name = "xuefly", Birthday = DateTime.Now.AddYears(-20) };

Assert.IsNotNull(person);
}
[TestCase]
[ExpectedException(
typeof(IsNullOrEmptyException))]
public void NameShouldNotBeNullOrEmpty()
{
Person person =
new Person { Name = "", Birthday = DateTime.Now.AddYears(-20) };
}
[TestCase]
[ExpectedException(
typeof(ValidationException))]
public void BirthdayShouldNotGreatThanNow()
{
Person person =
new Person { Name = "xuefly", Birthday = DateTime.Now.AddYears(1) };
}
}

如愿的抛出了异常。

2,Validate()方法验证

给每一个Domain Model实现一个“Validate()”方法。在这种情况下,我们在使用Domain Model的时候主动调用该方法来验证对象的合法性,如果验证失败直接抛出异常或者返回一个类似List<ValidationError>这样形式的集合。这样的话,约束Domain Model的“法律/规则”可以被违犯,执不执行法律的权利交给了上层的用户,比较灵活但同时上层的用户肩负起了责任。(就像google搞出来的那几个“google事件”一样,google违犯了我们的法律,确定无疑!皮球已经踢到了我们这边,处不处罚google是我们必须要决定的事情)

我们先定义一个用来封装业务规则的类叫BrokenBusinessRule:

public class BrokenBusinessRule
{
public BrokenBusinessRule(string property, string rule)
{
Property = property;
Rule = rule;
}

public string Property { get; set; }
public string Rule { get; set; }
}

下面我们针对BlogSite来书写验证:其中的GetBrokenRules()方法就是Validate() Method:

public class BlogSite : Entity
{
protected bool IsValidated = false;

public string BlogTitle { get; set; }

public string DomainName { get; set; }

public BlogUser BlogOwner { get; set; }

// 该方法就是Validate() Method
public List<BrokenBusinessRule> GetBrokenRules()
{
List<BrokenBusinessRule> brokenRules;

if (IsValidated)
{
return new List<BrokenBusinessRule>();
}

// 下面的代码参考http://www.cnblogs.com/tristanguo/archive/2009/05/15/1457197.html 感谢tristanguo
brokenRules = new Validator<BlogSite>(this).Validate(b => BlogTitle.IsNullOrEmpty(), new BrokenBusinessRule("BlogTitle", "博客名称不能为空"))
.Validate(b => DomainName.IsNullOrEmpty(),
new BrokenBusinessRule("DomainName", "域名不能为空"))
.BrokenRoles;

brokenRules.AddRange(BlogOwner.GetBrokenRules());

IsValidated = brokenRules.Count ==
0;

return brokenRules;
}
}

同样,我们使用NUnit测试一下:

[TestFixture]
public class BlogUserTest

{

[TestCase]
public void TestBlogUser()
{

BlogUser blogUser = new BlogUser { LoginID="", Name="", Birthday=DateTime.Now.AddYears(1), Password=""};

Assert.IsTrue(blogUser.GetBrokenRules().Count == 4);

}

[TestCase]
public void TestBlogUser1()
{

// Name和Birthday是在BlogUser的基类Person中定义的
BlogUser blogUser = new BlogUser { LoginID = "xuefly", Name = "", Birthday = DateTime.Now.AddYears(1), Password = "123456" };

Assert.IsTrue(blogUser.GetBrokenRules().Count == 2);

}

[TestCase]
public void TestBlogUser2()

{

// 1:LoginID为空;2:密码为空
BlogUser blogUser = new BlogUser { LoginID = "", Name = "xuefly", Birthday = DateTime.Now.AddYears(-20), Password = "" };// Name和Birthday是在BlogUser的基类Person中定义的

Assert.IsTrue(blogUser.GetBrokenRules().Count == 2);

}
}

测试也通过了。

3, 验证服务

这个类别是我通过观察Oxite2的验证机制分出来的,叫法不一定正确,下一篇书写。

欢迎加入Oxite小组一起学习:博客园Oxite小组

4, 基于配置的验证

这个我现在还不是很清楚,不知道跟我想象的是一回事不,等弄清楚了再书写吧。

代码这里下载:下载代码



如何向妻子解释OOD
OOAD与UML笔记
UML类图与类的关系详解
UML统一建模语言初学
总结一下领域模型的验证
基于 UML 的业务建模


面向对象的分析设计
基于UML的面向对象分析设计
UML + 嵌入式系统分析设计
关系数据库面向OOAD设计
业务建模与业务架构
使用用例进行需求管理


某航空IT部门 业务分析与业务建模
联想 业务需求分析与建模
北京航管科技 EA工具与架构设计
使用EA和UML进行嵌入式系统分析
全球最大的茶业集团 UML系统分析
华为 基于EA的嵌入式系统建模
水资源服务商 基于EA进行UML建模
更多...