DDD分层架构之值对象(层超类型篇)

上一篇介绍了值对象的基本概念,得到了一些朋友的支持,另外也有一些朋友提出了不同意见。这其实是很自然的事情,设计本来就充满了各种可能性,没有绝对正确的做法,只有更好的实践。但是设计与实践的好与坏,对于不同的人,以及处于不同的环境都有不同的诠释,这是一个仁者见仁,智者见智的问题。DDD非常抽象,以至于它的每一个概念,对于不同的人都有不同的看法,更何况基于DDD的.Net实践,就更难分辨哪一个用法更标准、更正宗。

  我对DDD的认识虽然还很肤浅,用得也很山寨,但这可能更加适合初步接触DDD的朋友。还是那句老话,你不是搞学术研究的,你并不需要挖掘DDD的学术价值,而是要把它切实的用到你的项目上,并产生回报。你不应该问对或错,而应该多看看哪些东西对你真正起作用,一方面需要多学习DDD理论知识,另一方面可以多参考其它人的用法,并琢磨出一套适合自己习惯的架构。特别是初学DDD的朋友,这一点更加重要,DDD水很深,盲目的采用某些你搞不懂的技术,只会增加负担。你也不需要把DDD所有东西都用起来,使用DDD不是为了赶时髦,如果某些东西让你感觉复杂,你先了解下就可以了,把搞懂的东西加入你的工具箱,然后项目上慢慢体验,时间稍长,你就能产生突破并从中受益。但你如果人云亦云,把注意力放到纯概念和一些名词术语上,把别人的经验生搬硬套到自己的项目,由于别人的思想你可能没有真正搞懂,另外别人的项目需求、团队水平、所用技术可能和你都不同,这样可能导致你维护了一个庞大的架构,但却没有捞到一丁点好处。

  我这个系列重点不在DDD,而是如何搭建自己的应用程序框架。介绍DDD分层架构只是保证本系列的完整性,所以我不会非常详细的介绍。另外很多朋友迫切需要示例,我在此回复一下,本系列前期主要进行框架建设,包括一些公共操作类和层超类型,待底子打牢之后,我会向大家展示我的山寨DDD用法,以及如何通过应用程序框架快速开发项目。之所以不上来就搞一堆代码,是希望你通过这个系列能真正受益,你不仅需要知道框架怎么用,更需要知道这玩意是怎么弄出来的,以及重要代码的思考和演化过程。所以我写得可能非常啰嗦,我希望.Net初学者也能看懂。我的时间比较有限,更新时间不会太快,不过只要有人愿意继续看,我会坚持写完它。

  下面回到正文上来,本篇将完成DDD值对象的层超类型开发,所有代码都从网上搜集整理,如果大家有更好的请把你的代码发上来供大家参考,另外最好详细介绍你的代码为何更好,以免大家凭空瞎猜。

  首先,在Util.Domains类库中创建一个名为ValueObjectBase的抽象类。

  考虑值对象的相等性测试,怎样才能认为两个值对象是相等的?这可以通过比较两个值对象的所有属性值都相等来判断,换句话说,两个值对象有任何一个属性值不同,都不相等。我们需要重写Equals、GetHashCode 、==、!=这几个方法或运算符。

  在相等性比较中,我们可以通过反射来获取所有属性,并一一比较,以测试相等性。另外,GetHashCode将各属性值的哈希码使用简单的异或操作计算出来。如果觉得性能不好,子类可以重写相关实现。

  另外,值对象有时候需要创建一个副本,可以增加一个克隆方法Clone,采用浅表复制进行创建,由于值对象不可变,所以不同的值对象共享相同的属性值就不是什么问题。为了让Clone更加好用,可以让它创建出强类型的值对象,而不是一个object,这需要将值对象层超类型修改为泛型,将值对象作为泛型参数传递到基类。

  另外,前面介绍的实体状态输出和验证方法对值对象同样适用,所以需要在实体和值对象层超类型之上再增加一个基类,命名为DomainBase。

  由于值对象层超类型比较简单,我就简要介绍到这,下面是相关代码,如有疑问请留言。

  测试样例Address类代码如下,为了简单,我只留下两个属性。

namespace Util.Domains.Tests.Samples {
/// <summary>
/// 地址
/// </summary>
public class Address : ValueObjectBase<Address> {
/// <summary>
/// 初始化地址
/// </summary>
/// <param name="city">城市</param>
/// <param name="street">街道</param>
public Address( string city, string street ) {
City = city;
Street = street;
} /// <summary>
/// 城市
/// </summary>
public string City { get; private set; }
/// <summary>
/// 街道
/// </summary>
public string Street { get; private set; }
}
}

  值对象单元测试类ValueObjectBaseTest代码如下。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Util.Domains.Tests.Samples; namespace Util.Domains.Tests {
/// <summary>
/// 值对象测试
/// </summary>
[TestClass]
public class ValueObjectBaseTest {
/// <summary>
/// 地址1
/// </summary>
private Address _address1;
/// <summary>
/// 地址2
/// </summary>
private Address _address2;
/// <summary>
/// 地址3
/// </summary>
private Address _address3; /// <summary>
/// 测试初始化
/// </summary>
[TestInitialize]
public void TestInit() {
_address1 = new Address("a","b");
_address2 = new Address( "a", "b" );
_address3 = new Address( "1", "" );
} /// <summary>
/// 测试对象相等性
/// </summary>
[TestMethod]
public void TestEquals() {
Assert.IsFalse( _address1.Equals( null ) );
Assert.IsFalse( _address1 == null );
Assert.IsFalse( null == _address1 );
Assert.IsFalse( _address1.Equals(new Test()) );
Assert.IsTrue( _address1.Equals( _address2 ), "_address1.Equals( _address2 )" );
Assert.IsTrue( _address1 == _address2, "_address1 == _address2" );
Assert.IsFalse( _address1 != _address2, "_address1 != _address2" );
Assert.IsFalse( _address1 == _address3, "_address1 == _address3" );
} /// <summary>
/// 测试哈希
/// </summary>
[TestMethod]
public void TestGetHashCode() {
Assert.IsTrue( _address1.GetHashCode() == _address2.GetHashCode(), "_address1.GetHashCode() == _address2.GetHashCode()" );
Assert.IsFalse( _address1.GetHashCode() == _address3.GetHashCode(), "_address1.GetHashCode() == _address3.GetHashCode()" );
} /// <summary>
/// 测试克隆
/// </summary>
[TestMethod]
public void TestClone() {
_address3 = _address1.Clone();
Assert.IsTrue( _address1 == _address3 );
}
}
}

  DomainBase代码如下。

using System.Collections.Generic;
using System.Text;
using Util.Validations; namespace Util.Domains {
/// <summary>
/// 领域层顶级基类
/// </summary>
public abstract class DomainBase { #region 构造方法 /// <summary>
/// 初始化领域层顶级基类
/// </summary>
protected DomainBase() {
_rules = new List<IValidationRule>();
_handler = new ValidationHandler();
} #endregion #region 字段 /// <summary>
/// 描述
/// </summary>
private StringBuilder _description;
/// <summary>
/// 验证规则集合
/// </summary>
private readonly List<IValidationRule> _rules;
/// <summary>
/// 验证处理器
/// </summary>
private IValidationHandler _handler; #endregion #region ToString(输出领域对象的状态) /// <summary>
/// 输出领域对象的状态
/// </summary>
public override string ToString() {
_description = new StringBuilder();
AddDescriptions();
return _description.ToString().TrimEnd().TrimEnd( ',' );
} /// <summary>
/// 添加描述
/// </summary>
protected virtual void AddDescriptions() {
} /// <summary>
/// 添加描述
/// </summary>
protected void AddDescription( string description ) {
if ( string.IsNullOrWhiteSpace( description ) )
return;
_description.Append( description );
} /// <summary>
/// 添加描述
/// </summary>
protected void AddDescription<T>( string name, T value ) {
if ( string.IsNullOrWhiteSpace( value.ToStr() ) )
return;
_description.AppendFormat( "{0}:{1},", name, value );
} #endregion #region SetValidationHandler(设置验证处理器) /// <summary>
/// 设置验证处理器
/// </summary>
/// <param name="handler">验证处理器</param>
public void SetValidationHandler( IValidationHandler handler ) {
if ( handler == null )
return;
_handler = handler;
} #endregion #region AddValidationRule(添加验证规则) /// <summary>
/// 添加验证规则
/// </summary>
/// <param name="rule">验证规则</param>
public void AddValidationRule( IValidationRule rule ) {
if ( rule == null )
return;
_rules.Add( rule );
} #endregion #region Validate(验证) /// <summary>
/// 验证
/// </summary>
public virtual void Validate() {
var result = GetValidationResult();
HandleValidationResult( result );
} /// <summary>
/// 获取验证结果
/// </summary>
private ValidationResultCollection GetValidationResult() {
var result = ValidationFactory.Create().Validate( this );
Validate( result );
foreach ( var rule in _rules )
result.Add( rule.Validate() );
return result;
} /// <summary>
/// 验证并添加到验证结果集合
/// </summary>
/// <param name="results">验证结果集合</param>
protected virtual void Validate( ValidationResultCollection results ) {
} /// <summary>
/// 处理验证结果
/// </summary>
private void HandleValidationResult( ValidationResultCollection results ) {
if ( results.IsValid )
return;
_handler.Handle( results );
} #endregion
}
}

  ValueObjectBase代码如下。

using System;
using System.Linq; namespace Util.Domains {
/// <summary>
/// 值对象
/// </summary>
/// <typeparam name="TValueObject">值对象类型</typeparam>
public abstract class ValueObjectBase<TValueObject> : DomainBase, IEquatable<TValueObject> where TValueObject : ValueObjectBase<TValueObject> { #region Equals(相等性比较) /// <summary>
/// 相等性比较
/// </summary>
public bool Equals( TValueObject other ) {
return this == other;
} /// <summary>
/// 相等性比较
/// </summary>
public override bool Equals( object other ) {
return Equals( other as TValueObject );
} #endregion #region ==(相等性比较) /// <summary>
/// 相等性比较
/// </summary>
public static bool operator ==( ValueObjectBase<TValueObject> valueObject1, ValueObjectBase<TValueObject> valueObject2 ) {
if ( (object)valueObject1 == null && (object)valueObject2 == null )
return true;
if ( (object)valueObject1 == null || (object)valueObject2 == null )
return false;
if ( valueObject1.GetType() != valueObject2.GetType() )
return false;
var properties = valueObject1.GetType().GetProperties();
return properties.All( property => property.GetValue( valueObject1 ) == property.GetValue( valueObject2 ) );
} #endregion #region !=(不相等比较) /// <summary>
/// 不相等比较
/// </summary>
public static bool operator !=( ValueObjectBase<TValueObject> valueObject1, ValueObjectBase<TValueObject> valueObject2 ) {
return !( valueObject1 == valueObject2 );
} #endregion #region GetHashCode(获取哈希) /// <summary>
/// 获取哈希
/// </summary>
public override int GetHashCode() {
var properties = GetType().GetProperties();
return properties.Select( property => property.GetValue( this ) )
.Where( value => value != null )
.Aggregate( 0, ( current, value ) => current ^ value.GetHashCode() );
} #endregion #region Clone(克隆副本) /// <summary>
/// 克隆副本
/// </summary>
public virtual TValueObject Clone() {
return (TValueObject)MemberwiseClone();
} #endregion
}
}

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

  下载地址:http://files.cnblogs.com/xiadao521/Util.2014.11.27.1.rar

版权所有,转载请注明出处 何镇汐的技术博客

DDD分层架构之值对象(层超类型篇)的更多相关文章

  1. 应用程序框架实战二十二 : DDD分层架构之仓储(层超类型基础篇)

    前一篇介绍了仓储的基本概念,并谈了我对仓储的一些认识,本文将实现仓储的基本功能. 仓储代表聚合在内存中的集合,所以仓储的接口需要模拟得像一个集合.仓储中有很多操作都是可以通用的,可以把这部分操作抽取到 ...

  2. 应用程序框架实战十七:DDD分层架构之值对象(层超类型篇)

    上一篇介绍了值对象的基本概念,得到了一些朋友的支持,另外也有一些朋友提出了不同意见.这其实是很自然的事情,设计本来就充满了各种可能性,没有绝对正确的做法,只有更好的实践.但是设计与实践的好与坏,对于不 ...

  3. 应用程序框架实战十六:DDD分层架构之值对象(介绍篇)

    前面介绍了DDD分层架构的实体,并完成了实体层超类型的开发,同时提供了验证方面的支持.本篇将介绍另一个重要的构造块——值对象,它是聚合中的主要成分. 如果说你已经在使用DDD分层架构,但你却从来没有使 ...

  4. DDD分层架构之值对象(介绍篇)

    DDD分层架构之值对象(介绍篇) 前面介绍了DDD分层架构的实体,并完成了实体层超类型的开发,同时提供了验证方面的支持.本篇将介绍另一个重要的构造块——值对象,它是聚合中的主要成分. 如果说你已经在使 ...

  5. DDD分层架构之仓储

    DDD分层架构之仓储(层超类型基础篇) 前一篇介绍了仓储的基本概念,并谈了我对仓储的一些认识,本文将实现仓储的基本功能. 仓储代表聚合在内存中的集合,所以仓储的接口需要模拟得像一个集合.仓储中有很多操 ...

  6. 4、传统三层架构与DDD分层架构

    4.传统三层架构与DDD分层架构 模型是抽象的 现实是形象的 技巧是重要的 思想是永恒的 从传统三层架构与DDD分层架构的编程演变其实是思想的演变. 传统三层架构,即用户界面层UI.业务逻辑层BAL. ...

  7. 【转载】DDD分层架构的三种模式

    引言 在讨论DDD分层架构的模式之前,我们先一起回顾一下DDD和分层架构的相关知识. DDD DDD(Domain Driven Design,领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高 ...

  8. DDD分层架构的三种模式

    引言 在讨论DDD分层架构的模式之前,我们先一起回顾一下DDD和分层架构的相关知识. DDD DDD(Domain Driven Design,领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高 ...

  9. 应用程序框架实战十八:DDD分层架构之聚合

    前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可以让业务逻辑高度内聚, ...

随机推荐

  1. POJ 2531-Network Saboteur(DFS)

    Network Saboteur Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 9435   Accepted: 4458 ...

  2. Android有关JNI 学习(两)为JNI方法名称,数据类型和方法签名的一些知识

    我们知道,使用javah产生c/c++当在头文件,将java定义 native 功能,以产生相应jni层功能,如下面: /* * Class: com_lms_jni_JniTest * Method ...

  3. Android MotionEvent事故响应机制

    于android于.主要活动包括点击.按.拖累.滑动等操作,这些构成了Android事件响应,总体而言,,所有事件由例如以下三部分构成的基础: 按(action_down),搬家(action_mov ...

  4. cocos2d-x3.0 解释具体的新的物理引擎setCategoryBitmask()、setContactTestBitmask()、setCollisionBitmask()

    转载请注明出处:游戏开发实验室http://blog.csdn.net/u010019717/article/details/32942641 我在编写游戏的时候遇到了这个问题.  物理引擎其它的内容 ...

  5. SQL入门学习2-聚合与排序

    3-1 对表进行聚合查询 聚合函数 所谓聚合,就是将多行汇总为一行. 函数名 功能 COUNT 计算表中的记录数(行数) SUM 计算表中数值列的数据合计值 AVG 计算表中数值列的数据平均值 MAX ...

  6. python使用smtplib库和smtp.qq.com邮件服务器发送邮件(转)

    使用qq的邮件服务器需要注意的两个地方主要是: 1.协议问题 使用465端口 SSL 协议 2.口令问题 出现SMTPAuthenticationError 主要的原因就是口令和帐号信息不对,这里我们 ...

  7. (一)IE8以下background不起作用

    最近遇到了些小bug,在这里做个记录分享下.低版本的浏览器不支持新属性或者说对代码的解析方式不一样,导致最后解析出来的结果五花八门,各种浏览器之间的兼容的确是一个令开发人员头疼的事情,不过有时候换一个 ...

  8. java 它 引用(基本类型的包装,构造函数和析构函数c++不同)

      一个:java 和c++参考控制 他提到引用,我们会想到java它不喜欢c++里面的指针.当然java内引用和c++里面的引用是不同的. 比如: 比方C++中,我对某一个函数的声明.int a(i ...

  9. Android WebView坑摘要

    要抓好近期iPad HybridApp至Android举,坑遇到太多.让我折腾过Android临近4在退伍军人头痛! 今天前者被列出,以满足,然后慢慢自己解决.现在,它已经解决android键盘覆盖问 ...

  10. Redis安装与基本配置(转)

    一.下载与安装 wget http://download.redis.io/releases/redis-3.0.0.tar.gz tar -zxvf redis-3.0.0.tar.gz -c /u ...