【渐进】浅尝DDD,对"试卷"建模

      领域模型是OO分析中最重要的和经典的模型。领域驱动设计(DDD)则是有效的软件复杂性的应对之道。

    领域模型其实是一种语言,领域专家与分析人员、开发人员之间交流的通用语言。 一开始,分析人员与领域专家需要对这个通用语言达成一致,双方能熟练的运用领域模型描述问题,表达、分析、处理问题。
    1. 领域模型不是图,图只是让核心、关键的概念清晰的呈现出来。图的表达能力有限,模型必须配备描述(需求采集会议中的口头描述,或文档中的文字描述),将图形所代表的意义,以及图形中没有呈现出来的规则、断言、细节进行补充,才能完整地表述需求。
    2. 领域模型的UML或者类UML图不能太细太完整,否则过于庞大的模型会干扰人的思维,阻碍对主要部分,或者复杂逻辑的梳理。业务总是被切分成一个个片断进行分析,在每一个片断里,画出几个主要的对象和交互逻辑,细节的部分用文字记录、描述。
    3. 领域模型中不应当出现设计、技术方面的术语,也不应当出现开发人员不理解的业务术语。

      关于领域模型表达方法(UML图Visio图)的选择问题,标准的形式是使用UML,但如果领域专家或开发人员根本无法对UML图例形成清晰的概念(将UML图映射到领域对象、逻辑,或代码实现),将注定DDD方式应用失败。因为没有掌握语言就无法使用它正确交流。第三节中强调建模人员要直接与开发过程接触,重要的一个作用是确认开发人员对领域模型这个语言是否正确理解,确保代码实现保持了建模者要表达的意图。因此DDD的一个关键思想是建模人员自始至终维护领域模型这个语言,以及它表达的内容,向团队的各个角色(领域专家、开发人员等)诠释各个建模元素的含义,让各个角色掌握这个语言,运用它来表达、实现实际需求。这是分析人员最关键的职责,贯穿整个过程。

      领域逻辑的表达不要受UML图的约束,否则可能是问题所在,适当的描述就可以很好的解决这个问题。

      关于DDD和UML相关的具体介绍可参考:

    《UML和模式应用》(第三版)

    《.NET.Domain.Driven.Design.with.C.Sharp》

      从领域问题,场景中分析找出概念类,确定一组概念类则是OO分析的核心。以下就“试卷”这个领域来进行DDD的分析和设计尝试。

  • 场景描述

      试卷由多个大题组成,每个大题下有多道试题,试卷还可以有多个分卷,如分卷I,分卷II,可以统计大题下的试题信息…

  • 概念类提取

      从上述描述提取出概念类:试卷,分卷,大题,试题。

  • UML概念类表达

OL_V2.0

  • 几个设计方案

     方案一:按级联关系相应设计试卷结构,形成试卷->分卷->大题->试题的结构。

     方案二:将试卷的分卷,大题,试题都认为是试卷分块,试卷成为一个试卷分块的线性列表。

     方案三:将试卷的分卷,大题等归属于结构相关信息,对试卷抽象出试题列表和试卷结构,

  • 方案比较(略)

     方案一完全依赖场景描述设计层级结构,固化了试卷结构,方案二,三的设计思路都是将试题列表设计为线性,屏蔽业务和呈现需求的差异。

  • 最终方案

     试卷(Paper)主要由试卷结构(PaperStruct)和试题列表(PaperQuestionList)构成,试卷结构包含试卷分块列表,试卷分块用于表示试卷中的大题概念。

     试题列表是为此场景而特化设计的集合,仅仅是对试卷分块(PaperPart)中试题列表的逻辑映射,仅公开部分集合相关操作。

     试卷分块主要包含试题索引范围(QuestionIndexRang)和 分块试题列表(QuestionList)的定义以及相关统计属性。

     最终方案的优势:即保证了结构和业务的剥离,又使得呈现可以以直观的层级进行。

  • UML详细设计模型

OL_V2.0

  • 基本代码成型

     试卷:

  1.   /// <summary>
  2.   /// 试卷
  3.   /// </summary>
  4.   public class Paper : ICloneable
  5.   {
  6.       private int _paperId;
  7.       /// <summary>
  8.       /// 获取试卷标识
  9.       /// </summary>
  10.       public int PaperId { get { return this._paperId; } }
  11.       /// <summary>
  12.       /// 获取或设置试卷结构
  13.       /// </summary>
  14.       public PaperStruct PaperStruct { get; set; }
  15.       /// <summary>
  16.       /// 获取试卷分块列表
  17.       /// </summary>
  18.       public PaperPartList PaperPartList
  19.       {
  20.           get { return this.PaperStruct.PaperPartList; }
  21.       }
  22.       /// <summary>
  23.       /// 获取试卷试题列表
  24.       /// </summary>
  25.       public PaperQuestionList QuestionList
  26.       {
  27.           get { return PaperQuestionList.Create(this); }
  28.       }
  29.       /// <summary>
  30.       /// 清理试卷内部结构
  31.       /// </summary>
  32.       public void Clear()
  33.       {
  34.           //重设分块的试题索引
  35.           //重设试题列表索引
  36.       }
  37.  
  38.       #region ICloneable 成员
  39.       /// <summary>
  40.       /// 返回一份当前试卷的副本
  41.       /// 副本将完成可靠的拷贝逻辑
  42.       /// </summary>
  43.       /// <returns></returns>
  44.       public object Clone()
  45.       {
  46.           Paper clone = this.MemberwiseClone() as Paper;
  47.  
  48.           //引用处理
  49.           //...
  50.  
  51.           //标识置0
  52.           clone._paperId = 0;
  53.  
  54.           //拷贝引用
  55.  
  56.           return clone;
  57.       }
  58.       #endregion
  59.  
  60.  
  61.   }

 

    试卷分块:分块将维持试题索引范围

  1. /// <summary>
  2. /// 试卷分块
  3. /// </summary>
  4. public class PaperPart : ICloneable
  5. {
  6.     //构?
  7.  
  8.     public PaperPart()
  9.     {
  10.         this.QuestionRang = new QuestionIndexRang(-1, -1);
  11.         this.QuestionList = new List<QuestionInfo>();
  12.     }
  13.  
  14.     /// <summary>
  15.     /// ?取或?置分块下的??索引?围 ?不存在??则索引??为-1
  16.     /// </summary>
  17.     public QuestionIndexRang QuestionRang { get; set; }
  18.     /// <summary>
  19.     /// ?取或?置?分块下的??列?
  20.     /// </summary>
  21.     public List<QuestionInfo> QuestionList { get; set; }
  22.  
  23.     #region ICloneable 成员
  24.  
  25.     public object Clone()
  26.     {
  27.         PaperPart clone = this.MemberwiseClone() as PaperPart;
  28.  
  29.         //处理引用
  30.  
  31.         return clone;
  32.     }
  33.  
  34.     #endregion
  35. }

 

    试卷试题列表:此设计用于映射分块中的试题,并控制对其的调用,使得对它的相关操作能映射至实际的试题。

  1. /// <summary>
  2. /// 试卷的试题列表
  3. /// </summary>
  4. public class PaperQuestionList : CustomList<QuestionInfo>
  5. {
  6.     /// <summary>
  7.     /// 所在的?卷
  8.     /// </summary>
  9.     public Paper Paper { get; private set; }
  10.     /// <summary>
  11.     /// 初始化
  12.     /// </summary>
  13.     /// <param name="paper"></param>
  14.     private PaperQuestionList(Paper paper)
  15.         : base()
  16.     {
  17.         this.Paper = paper;
  18.     }
  19.     /// <summary>
  20.     /// 生成试卷的试题列表
  21.     /// </summary>
  22.     public static PaperQuestionList Create(Paper paper)
  23.     {
  24.         PaperQuestionList list = new PaperQuestionList(paper);
  25.  
  26.         //拼接
  27.         list.Paper.PaperPartList.ForEach(o => { list._list.AddRange(o.QuestionList as IEnumerable<QuestionInfo>); });
  28.  
  29.         return list;
  30.     }
  31.     /// <summary>
  32.     /// 重载索引器
  33.     /// </summary>
  34.     /// <param name="index"></param>
  35.     /// <returns></returns>
  36.     public override QuestionInfo this[int index]
  37.     {
  38.         get
  39.         {
  40.             return base[index];
  41.         }
  42.         set
  43.         {
  44.             bool flag = false;
  45.             int count = 0;
  46.             for (int i = 0; i < this.Paper.PaperPartList.Count; i++)
  47.             {
  48.                 if (flag) break;
  49.  
  50.                 for (int j = 0; j < this.Paper.PaperPartList[i].QuestionList.Count; j++)
  51.                 {
  52.                     if (count == index)
  53.                     {   
  54.                         //修正实际引用
  55.                         this.Paper.PaperPartList[i].QuestionList[j] = value;
  56.                         flag = true;
  57.                         break;
  58.                     }
  59.                     count++;
  60.                 }
  61.             }
  62.             base[index] = value;
  63.         }
  64.     }
  65. }

 

  • 更多考虑

     对于模型的接口和属性暴露:本文所提及的设计并非采用贫血模型,模型内部需维持主要逻辑,对于对外接口和属性应慎重,如对于引用类型的属性,应考虑使用者有可能并不总是按照设计者的预期来使用,因此最小化设计原则和按需暴露接口,或者返回接口而非真实引用尤为必要。

     领域模型并非为传输而设计,若希望模型同时能够支持跨层传输和序列化,则需额外注意相关属性和字段的设计和暴露。

     DDD是从业务出发,关注业务,以业务价值为导向的设计方法,测试和业务的实现均是由它出发,以对象而不是关系数据库作为模型基础,而对于存储则不是业务人员需要过多关心,通常web开发者习惯了数据驱动设计,总是希望实体和数据库一一映射,那么您恐怕需要换个思路思考了,因而一个强有力的ORM框架支持也是DDD的执行的保证。

 

     篇后语:学习和实践中,请多多指正。欢迎拍砖。

转载于:https://www.cnblogs.com/wsky/archive/2009/10/27/1590873.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一共两个压缩分卷,这是第一个分卷 第ⅰ部分 让领域模型发挥作用. 第1章 消化知识 5 1.1 有效建模的因素 9 1.2 知识消化 10 1.3 持续学习 11 1.4 知识丰富的设计 12 1.5 深层模型 15 第2章 交流及语言的使用 17 2.1 通用语言 17 2.2 利用对话改进模型 22 2.3 一个团队,一种语言 24 2.4 文档和图 25 2.4.1 书面的设计文档 27 2.4.2 执行的基础 29 2.5 说明性模型 29 第3章 将模型和实现绑定 32 3.1 模型驱动设计 33 3.2 建模范型和工具支持 36 3.3 突出主旨:为什么模型对用户很关键 41 3.4 实践型建模人员 43 .第ⅱ部分 模型驱动设计的构建块 第4章 分离领域 47 4.1 分层架构 47 4.1.1 层间的联系 51 4.1.2 架构框架 51 4.2 模型属于领域层 52 4.3 其他种类的隔离 55 第5章 软件中的模型描述 56 5.1 关联 57 5.2 实体(又称引用对象) 62 5.2.1 实体建模 65 5.2.2 设计标识操作 66 5.3 值对象 68 5.3.1 设计值对象 71 5.3.2 设计包含值对象的关联 73 5.4 服务 74 5.4.1 服务和分隔的领域层 75 5.4.2 粒度 77 5.4.3 访问服务 77 5.5 模块(包) 77 5.5.1 敏捷的模块 79 5.5.2 基础结构驱动打包的缺陷 80 5.6 建模范式 82 5.6.1 对象范式的优势 82 5.6.2 对象世界中的非对象 84 5.6.3 在混合范式中使用模型驱动设计 85 第6章 领域对象的生命周期 87 6.1 聚合 88 6.2 工厂 96 6.2.1 工厂及其应用场所的选择99 6.2.2 只需构造函数的情况 101 6.2.3 接口的设计 102 6.2.4 如何放置不变量的逻辑 103 6.2.5 实体工厂与值对象工厂 103 6.2.6 存储对象的重建 103 6.3 仓储 105 6.3.1 查询仓储 109 6.3.2 了解仓储实现的必要性 111 6.3.3 实现仓储 111 6.3.4 在框架内工作 113 6.3.5 与工厂的关系 113 6.4 为关系数据库设计对象 115 第7章 使用语言:扩展示例 117 7.1 货物运输系统概述 117 7.2 隔离领域:系统简介 119 7.3 区分实体和值对象 120 7.4 运输领域中的关联设计 121 7.5 聚合的边界 123 7.6 选择仓储 124 7.7 场景概述 125 7.7.1 应用特性示例:改变一件货物的目的地126 7.7.2 应用特性示例:重复业务126 7.8 对象的创建 126 7.8.1 cargo的工厂和构造函数 126 7.8.2 添加一个handling event127 7.9 停下来重构:cargo聚合的另一种设计 129 7.10 运输模型中的模块 131 7.11 引入新特性:配额检查 133 7.11.1 连接两个系统 134 7.11.2 改进模型:划分业务 135 7.11.3 性能调整 137 7.12 小结 137 第ⅲ部分 面向更深层解的重构 第8章 突破 143 8.1 关于突破的故事 144 8.1.1 中看不中用的模型 144 8.1.2 突破 146 8.1.3 更深层的模型 148 8.1.4 冷静的决定 149 8.1.5 成效 150 8.2 时机 150 8.3 着眼于根本 151 8.4 尾声:一连串的新理解 151 第9章 隐含概念转变为显式概念 153 9.1 概念挖掘 153 9.1.1 倾听表达用语 154 9.1.2 检查不协调之处 157 9.1.3 研究矛盾之处 162 9.1.4 查阅书籍 162 9.1.5 尝试,再尝试 164 9.2 如何建模不太明显的概念 164.. 9.2.1 显式的约束 165 9.2.2 作为领域对象的流程 167 9.2.3 规格 168 9.2.4 规格的应用和实现 171 第10章 柔性设计 184 10.1 释意接口 186 10.2 无副作用函数 190 10.3 断言 194 10.4 概念轮廓 197 10.5 孤立类 201 10.6 操作封闭 203 10.7 声明性设计 205 10.8 一个声明性风格的设计 207 10.9 攻击角度 215 10.9.1 切分子领域

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值