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. 记一次tomcat故障排查(转)

    1~1024之间的端口号是保留端口,通常是为特定目的预留的.虽然你的问题不是由于保留端口引起的,但是仍然建议你不要随意使用保留端口作为自定义服务的端口,如果你能早早遵循这一规则压根就不会遇到这个问题. ...

  2. hdu4758 Walk Through Squares (AC自己主动机+DP)

    Walk Through Squares Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others ...

  3. Linux内核分析(五)----字符设备驱动实现

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

  4. Qt原始资源形象问题后删除

        这些天Qt请项目超市收银系统,作为练一练手,无论如何,亦休闲亦无关,做几乎同样的.旨在取代以前的资源图片, 是什么改变了,码里面的路径都改了.还是编译只是去,总是提示这样一个错误. <s ...

  5. Unity3D 如何图形问题修正旋转模型已导入?

     如何纠正旋转模型被导入? 一些立体艺术资源包导出其模式,以便 Z 轴向上.Unity 大多数标准的脚本中假定的三维世界 Y 轴代表了.在 Unity 比改动脚本使其契合easy得多. Z 轴朝上 ...

  6. UVALive 5099 Nubulsa Expo 全球最小割 非网络流量 n^3

    主题链接:点击打开链接 意甲冠军: 给定n个点m条无向边 源点S 以下m行给出无向边以及边的容量. 问: 找一个汇点,使得图的最大流最小. 输出最小的流量. 思路: 最大流=最小割. 所以题意就是找全 ...

  7. Android设计模式(五岁以下儿童)--简单工厂模式

    1.面试的时候问这个问题: 在ListView 的item小程序.很多不同的显示风格.或者是,为了更好地维护,不同的样式,应该怎么做? 我一下就想到的是工厂的模式,利用project,编写ViewFa ...

  8. 持续集成Jenkins + robot framework + git

    Jenkins + robot framework + git持续集成 一.Jenkins安装插件 进入系统管理—插件管理—可选插件下安装以下插件Git Client Plugin.GIT plugi ...

  9. 查询出各个学科的前3名的同学信息的Sql

    查找各个学科的成绩前3名的学生信息Sql,有2种方法,一种是利用sql的row_number() over()函数,另一种是用子查询, 表设计如下 如果不考虑各个学科的成绩有并列的情况的话,有如下两种 ...

  10. hdu 2191 (多重背包+二进制优化)

    Problem Description 急!灾区的食物依然短缺!为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品, ...