learn.lianglianglee.com/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html
2022-05-11 19:04:14 +08:00

1173 lines
48 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>079 场景驱动设计与 DCI 模式.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/领域驱动设计实践(完)/001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html">001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html">002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/003 领域驱动设计概览.md.html">003 领域驱动设计概览.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/004 深入分析软件的复杂度.md.html">004 深入分析软件的复杂度.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/005 控制软件复杂度的原则.md.html">005 控制软件复杂度的原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/006 领域驱动设计对软件复杂度的应对(上).md.html">006 领域驱动设计对软件复杂度的应对(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/007 领域驱动设计对软件复杂度的应对(下).md.html">007 领域驱动设计对软件复杂度的应对(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/008 软件开发团队的沟通与协作.md.html">008 软件开发团队的沟通与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/009 运用领域场景分析提炼领域知识(上).md.html">009 运用领域场景分析提炼领域知识(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/010 运用领域场景分析提炼领域知识(下).md.html">010 运用领域场景分析提炼领域知识(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/011 建立统一语言.md.html">011 建立统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/012 理解限界上下文.md.html">012 理解限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/013 限界上下文的控制力(上).md.html">013 限界上下文的控制力(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/014 限界上下文的控制力(下).md.html">014 限界上下文的控制力(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/015 识别限界上下文(上).md.html">015 识别限界上下文(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/016 识别限界上下文(下).md.html">016 识别限界上下文(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/017 理解上下文映射.md.html">017 理解上下文映射.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/018 上下文映射的团队协作模式.md.html">018 上下文映射的团队协作模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/019 上下文映射的通信集成模式.md.html">019 上下文映射的通信集成模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/020 辨别限界上下文的协作关系(上).md.html">020 辨别限界上下文的协作关系(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/021 辨别限界上下文的协作关系(下).md.html">021 辨别限界上下文的协作关系(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/022 认识分层架构.md.html">022 认识分层架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/023 分层架构的演化.md.html">023 分层架构的演化.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/024 领域驱动架构的演进.md.html">024 领域驱动架构的演进.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/025 案例 层次的职责与协作关系(图文篇).md.html">025 案例 层次的职责与协作关系(图文篇).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/026 限界上下文与架构.md.html">026 限界上下文与架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/027 限界上下文对架构的影响.md.html">027 限界上下文对架构的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/028 领域驱动设计的代码模型.md.html">028 领域驱动设计的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/029 代码模型的架构决策.md.html">029 代码模型的架构决策.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/030 实践 先启阶段的需求分析.md.html">030 实践 先启阶段的需求分析.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/031 实践 先启阶段的领域场景分析(上).md.html">031 实践 先启阶段的领域场景分析(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/032 实践 先启阶段的领域场景分析(下).md.html">032 实践 先启阶段的领域场景分析(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">033 实践 识别限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html">034 实践 确定限界上下文的协作关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">035 实践 EAS 的整体架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html">036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/037 「战术篇」开篇词:领域驱动设计的不确定性.md.html">037 「战术篇」开篇词:领域驱动设计的不确定性.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/038 什么是模型.md.html">038 什么是模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/039 数据分析模型.md.html">039 数据分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/040 数据设计模型.md.html">040 数据设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/041 数据模型与对象模型.md.html">041 数据模型与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/042 数据实现模型.md.html">042 数据实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">043 案例 培训管理系统.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html">044 服务资源模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">045 服务行为模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/046 服务设计模型.md.html">046 服务设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/047 领域模型驱动设计.md.html">047 领域模型驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/048 领域实现模型.md.html">048 领域实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/049 理解领域模型.md.html">049 理解领域模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/050 领域模型与结构范式.md.html">050 领域模型与结构范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/051 领域模型与对象范式(上).md.html">051 领域模型与对象范式(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/052 领域模型与对象范式(中).md.html">052 领域模型与对象范式(中).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/053 领域模型与对象范式(下).md.html">053 领域模型与对象范式(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/054 领域模型与函数范式.md.html">054 领域模型与函数范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/055 领域驱动分层架构与对象模型.md.html">055 领域驱动分层架构与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/056 统一语言与领域分析模型.md.html">056 统一语言与领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/057 精炼领域分析模型.md.html">057 精炼领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/058 彩色 UML 与彩色建模.md.html">058 彩色 UML 与彩色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/059 四色建模法.md.html">059 四色建模法.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/060 案例 订单核心流程的四色建模.md.html">060 案例 订单核心流程的四色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/061 事件风暴与业务全景探索.md.html">061 事件风暴与业务全景探索.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/062 事件风暴与领域分析建模.md.html">062 事件风暴与领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/063 案例 订单核心流程的事件风暴.md.html">063 案例 订单核心流程的事件风暴.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/064 表达领域设计模型.md.html">064 表达领域设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/065 实体.md.html">065 实体.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/066 值对象.md.html">066 值对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/067 对象图与聚合.md.html">067 对象图与聚合.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/068 聚合设计原则.md.html">068 聚合设计原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/069 聚合之间的关系.md.html">069 聚合之间的关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/070 聚合的设计过程.md.html">070 聚合的设计过程.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/071 案例 培训领域模型的聚合设计.md.html">071 案例 培训领域模型的聚合设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/072 领域模型对象的生命周期-工厂.md.html">072 领域模型对象的生命周期-工厂.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/073 领域模型对象的生命周期-资源库.md.html">073 领域模型对象的生命周期-资源库.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/074 领域服务.md.html">074 领域服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/075 案例 领域设计模型的价值.md.html">075 案例 领域设计模型的价值.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/076 应用服务.md.html">076 应用服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/077 场景的设计驱动力.md.html">077 场景的设计驱动力.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">078 案例 薪资管理系统的场景驱动设计.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html">079 场景驱动设计与 DCI 模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">080 领域事件.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/081 发布者—订阅者模式.md.html">081 发布者—订阅者模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/082 事件溯源模式.md.html">082 事件溯源模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/083 测试优先的领域实现建模.md.html">083 测试优先的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/084 深入理解简单设计.md.html">084 深入理解简单设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/085 案例 薪资管理系统的测试驱动开发(上).md.html">085 案例 薪资管理系统的测试驱动开发(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">086 案例 薪资管理系统的测试驱动开发(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">087 对象关系映射(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">088 对象关系映射(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">089 领域模型与数据模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/090 领域驱动设计对持久化的影响.md.html">090 领域驱动设计对持久化的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/091 领域驱动设计体系.md.html">091 领域驱动设计体系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/092 子领域与限界上下文.md.html">092 子领域与限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/093 限界上下文的边界与协作.md.html">093 限界上下文的边界与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/094 限界上下文之间的分布式通信.md.html">094 限界上下文之间的分布式通信.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/095 命令查询职责分离.md.html">095 命令查询职责分离.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/096 分布式柔性事务.md.html">096 分布式柔性事务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/097 设计概念的统一语言.md.html">097 设计概念的统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/098 模型对象.md.html">098 模型对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/099 领域驱动设计参考过程模型.md.html">099 领域驱动设计参考过程模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/100 领域驱动设计的精髓.md.html">100 领域驱动设计的精髓.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/101 实践 员工上下文的领域建模.md.html">101 实践 员工上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/102 实践 考勤上下文的领域建模.md.html">102 实践 考勤上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/103 实践 项目上下文的领域建模.md.html">103 实践 项目上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/104 实践 培训上下文的业务需求.md.html">104 实践 培训上下文的业务需求.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/105 实践 培训上下文的领域分析建模.md.html">105 实践 培训上下文的领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/106 实践 培训上下文的领域设计建模.md.html">106 实践 培训上下文的领域设计建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/107 实践 培训上下文的领域实现建模.md.html">107 实践 培训上下文的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/108 实践 EAS 系统的代码模型.md.html">108 实践 EAS 系统的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/109 后记:如何学习领域驱动设计.md.html">109 后记:如何学习领域驱动设计.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>079 场景驱动设计与 DCI 模式</h1>
<h3>思维模式与设计方法</h3>
<p>对象是强调行为协作的,但对象自身却是对概念的描述。一旦我们将现实世界映射为对象,由于行为需要正确地分配给各个对象,于是行为就被打散了,缺少了领域场景的连续性。场景驱动设计引入“分解任务”的方法,一方面通过分而治之的思想降低了领域逻辑的复杂度,另一方面也建立了一系列连续的行为去表现领域场景,使得整个领域场景被分解的同时还能保证完整性。时序图体现了领域场景下行为的动态协作过程,并反向驱动出角色构造型来承担各自的职责,就能使得对象的设计变得更加合理。</p>
<p>分解任务之所以能够承担此重任,一个关键原因在于它匹配了软件开发人员的思维模式。在将业务需求转换为软件设计的过程中,要找到一种既具有业务视角又具有设计视角的思维模式,并非易事。任务分解采用面向过程的思维模式,以业务视角对领域场景进行观察和剖析;然后再采用面向对象的思维模式,以设计视角结合职责与角色构造型,形成对职责的角色分配。这两种视角的切换是自然的,它同时降低了需求理解和设计建模的难度。</p>
<p>软件设计终究是由人做出的决策,在提出一种设计方法时,若能从人的思维模式着手,就容易找到现实世界与模型世界的结合点。如果我们将领域场景视为电影或剧本中的场景,它反映了我们需要解决的代表问题域的现实世界。卡尔维诺在《看不见的城市》一书中描绘了这样的场景:</p>
<blockquote>
<p>梅拉尼亚的人口生生不息:对话者一个个相继死去,而接替他们对话的人又一个个出生,分别扮演对话中的角色。当有人转换角色,或者永远离开或者初次进入广场时,就会引起连锁式变化,直至所有角色都重新分配妥当为止。</p>
</blockquote>
<p>这个场景描述了一座奇幻的城市,这种城市的居民会聚集在广场中发生一场一场的对话,对话持续不断地继续下去,但是参与对话的角色却如幻影一般发生变化。这一幕小说情节很好地阐释了 DCI 模式,它开启了另外一种投影现实世界到对象世界的思维模式。</p>
<h3>DCI 模式</h3>
<p>DCI 模式认为在现实世界到对象世界的映射中构成元素只有三个数据Data、上下文Context和交互Interaction。在梅拉尼亚这座城市城市的广场是上下文城市的居民是数据他们扮演了不同的角色进行不同的对话这种对话就是交互。</p>
<p>和场景驱动设计相同DCI 模式需要从业务需求表现的现实世界中截取一幕场景作为设计的上下文。上下文将参与交互的数据“框定”起来,根据场景要达成的业务目标确定对象要扮演的角色,以及角色之间的交互行为。每个数据对象在扮演各自角色时,只能做出符合自己角色身份的行为,这些行为在 DCI 模式中被称之为“角色方法Role Method它们反映了数据的目的数据对象自身还拥有一些固定的行为称之为“本地方法Local Method它们反映了数据的特征。数据对象通过角色方法参与到上下文的交互通过本地方法访问和操作自身拥有的数据然后采用某种形式将角色绑定到对象之上</p>
<p><img src="assets/32381e00-e0e2-11e9-a074-f5ff05024c30" alt="50387535.png" /></p>
<p>现实世界有很多这样的例子。一个人在上下文中会扮演一种特定的角色,他与别的角色展开不同的交互行为。这时,人作为数据对象,具备 talk()、walk()、write() 等本地行为,这些本地行为与角色无关,属于人的固有行为。当一个人处于课堂学习上下文时,若扮演了教师角色,就会拥有角色行为 teach(),与之交互的角色为学生,角色行为是 learn()。teach() 与 learn() 这样的角色方法由 talk()、write() 等本地方法实现,本地方法不会随着上下文的变化而变化,因此属于数据对象最为稳定的领域逻辑。</p>
<p>一个数据对象可以同时承担多个角色,例如一个人既可以是教师,也可以是学生,回到家,面对不同的角色,他也在不断变换着角色:父亲、儿子、丈夫……显然,角色代表了一种身份或者一种能力,更像是一种接口行为。正如上图所示,当一个数据对象参与到上下文的交互中时,就需要将角色绑定到对象上,使得对象拥有角色行为。</p>
<h3>转账业务的DCI实现</h3>
<p>以银行的转账业务为例,它的上下文就是 TransferingContext储蓄账户 SavingAccount 作为体现了领域概念的数据对象参与到转账上下文中。按照 DCI 的思维模式,我们需要对上下文中的数据提出两个问题:</p>
<ul>
<li>它是什么?数据代表了上下文的领域概念;</li>
<li>它做了什么?角色代表了数据在上下文中的身份。</li>
</ul>
<p>虽然转账上下文牵涉到两个不同的储蓄账户对象,但各自扮演的角色却不相同。一个账户扮演了转出方 TransferSource另一个账户扮演了转入方 TransferTarget对应的角色方法就是 transferOut() 与 transferIn()。储蓄账户拥有余额数据,增加和减少余额值都是储蓄账户这个数据对象的固有特征,相当于针对余额数据进行的数学运算,对应的本地方法为 decrease() 与 increase()。</p>
<p>很明显,通过本地方法,数据回答了“它是什么”这个问题,体现了数据的本质特征,这样的行为通常不会发生变化;角色方法回答了“它做了什么”这一问题,操作了数据的业务规则,因此可能会频繁发生改变。一个稳定不变,一个频繁变化,自然就需要隔离它们,这就是角色的价值。最后,由上下文来指定角色,并管理角色之间的交互行为。采用 DCI 模式实现银行转账的代码如下所示:</p>
<pre><code class="language-scala">// Data
case class SavingAccount(val string owner, var balance: Double) {
// 几乎不掺杂业务逻辑,提供最纯粹的数据操作行为
def increase(amount: Double): Unit = this.balance += amount
def decrease(amount: Double): Unit = this.balance -= amount
}
// Role
trait TransferSource {
this:SavingAccount =&gt; //表示当前 trait 只能应用到 SavingAccount 上,并且混入它的对象,完成了角色和对象的绑定
// 具有业务行为的角色方法
def transferOut(amount: Double): Unit = {
if (this.balance &lt; amount) throw new NotEnoughBalanceException()
this.decrease(amount)
}
}
// Role
trait TransferTarget {
this:SavingAccount =&gt;
def transferIn(amount: Double): Unit = this.increase(amount)
}
// Context
class TransferContext(notification: NotificationService) {
def transfer(source: TransferSource, target: TransferTarget, amount: Double) {
src.transferOut(amount)
target.transferIn(amount)
notification.sendMessage()
}
}
</code></pre>
<p>我之所以选择 Scala 语言来表达 DCI 模式的实现,是因为 Scala 提供的 trait 与 Self Type 语法可以自然无缝地绑定数据对象与角色。如上代码中的 <code>this:SavingAccount =&gt;</code> 就是 Self Type 的实现形式,完成了 SavingAccount 对象向 trait 的混入mixin。在混入了数据对象后在代表角色的 trait 实现中,就可以通过 this 来访问混入的对象,完成角色方法对本地方法的调用。</p>
<p>创建 SavingAccount 对象时,需要通过如下代码完成角色与对象的绑定:</p>
<pre><code class="language-scala">val source = SavingAccount(accountName, balance) with TransferSource
val target = SavingAccount(accountName, balance) with TransferTarget
</code></pre>
<p>Java 8 虽然为接口引入了默认方法,但它缺乏 Scala 语言 Self Type 这样的语法,不能混入数据对象,因而无法在角色方法中访问数据对象,必须通过参数传入。例如转出方的角色接口:</p>
<pre><code class="language-java">public interface TransferSource {
// SavingAccount 通过参数传入
default void transferOut(SavingAccount srcAccount, Amount amount) {
if (srcAccount.getBalance().lessThan(amount)) {
throw new NotEnoughBalanceException();
}
srcAccount.decrease(amount);
}
}
</code></pre>
<p>同时,数据类 SavingAccount 还必须显式实现这两个接口:</p>
<pre><code class="language-java">public class SavingAccount implements TransferSource, TransferTarget {}
</code></pre>
<p>上下文类的实现与 Scala 基本相同:</p>
<pre><code class="language-java">public class TransferContext {
private NotificationService notification;
public void transfer(TransferSource source, TranserTarget, Amount amount) {
source.transferOut(amount);
target.transferIn(amount);
notification.sendMessage();
}
}
</code></pre>
<p>不管是 Scala 的 trait还是 Java 中的默认方法都为参与交互的角色提供了默认实现。但这种默认实现也限制了担任角色的数据类型。例如trait 通过 Self Type 限制了混入的类型只能是 SavingAccount 或它的子类Java 默认方法的参数也限制了传入的对象只能是 SavingAccount 类型或它的子类。这样做的好处是有利于角色方法的重用,但同时却失去了数据类的灵活性。</p>
<p>一个数据对象可以同时承担多个角色,反过来,一个角色也可能被多个不同的数据对象扮演。还是以转账业务为例,可能不仅是 SavingAccount 才能参与转账,例如通过银行的储蓄账户将钱转入到支付宝,就是由 AlipayAccount 担任转入方角色。如果 AlipayAccount 与 SavingAccount 之间没有任何关系,根据前面的实现,就无法将其传递给 TransferTarget 角色接口;同理,将支付宝的钱转入到储蓄账户,也受到了数据类型的限制。</p>
<p>如果将角色方法的实现留给数据类来实现,角色接口仅提供抽象的定义,就可以为各种不同的数据类戴上“角色”这顶帽子。站在上下文的角度看,它仅关心参与交互的角色方法,而不在意数据对象到底是什么。例如,在课堂学习上下文中,可以是一个人担任教师的角色,以 teach() 角色行为与学生交互,但也可以是一个 AI 机器人担任教师角色只要它的授课能够满足学生的需要即可。显然上下文从抽象角度看待参与交互的角色这就将角色分成了抽象和实现两个层次。这两个层次在DCI模式中分别称为 Methodful Role 与 Methodless Role。Methodful Role 组成了数据类,数据类对象则通过 Methodless Role 对外提供服务参与到上下文中。仍以转账上下文为例Methodless Role 的定义如下:</p>
<pre><code class="language-java">public interface TransferSource {
void transferOut(Amount amount);
}
public interface TransferTarget {
void transferIn(Amount amount);
}
</code></pre>
<p>这样的角色接口没有任何实现,仅仅规定了角色参与上下文交互的契约。数据类由本地方法和角色方法共同组成,其中它实现的角色方法代表了它是 Methodful Role</p>
<pre><code class="language-java">public class SavingAccount implements TransferSource, TransferTarget {
private Amount balance;
// Methodful Role 的角色方法
@Override
public void transferOut(Amount amount) {
if (balance.lessThan(amount)) {
throw new NotEnoughBalanceException();
}
decrease(amount);
}
// Methodful Role 的角色方法
@Override
public void transferIn(Amount amount) {
increase(amount);
}
// 本地方法
private void decrease(Amount amount) {
balance.substract(amount);
}
private void increase(Amount amount) {
balance.add(amount);
}
}
public class AlipayAccount implements TransferSource, TransferTarget {}
</code></pre>
<p>Methodful Role 与 Methodless Role 的分离不会影响角色的定义,因为上下文的交互是面向角色的,与数据类无关,不受数据类类型变化的任何影响,故而 TransferContext 的实现与前面的代码完全一样。</p>
<p>注意,无论采用 trait 的混入、实现接口的默认方法,还是 Methodless Role 与 Methodful Role 的分离最终都是由数据类型的对象来实现的。我认为DCI 模式将角色的承担者命名为数据类是一种糟糕的命名,因为数据这一说法极容易误导设计者,以为该类仅仅为上下文提供交互行为所需的数据。若产生这种误解,就有可能将数据类定义为贫血对象,设计出贫血模型。实际上,数据类更像是实体,在定义了数据属性之外,还需要定义属于自己的方法,即本地方法。这些方法同样表达了领域逻辑,只是该领域逻辑是与数据类强内聚的行为,如 SavingAccount 的 increase() 与 decrease() 方法。</p>
<h4>DCI 模式与场景驱动设计</h4>
<p>毫无疑问DCI 模式通过数据类、数据对象、角色、角色交互和上下文等设计元素共同实现了现实世界到对象世界的映射。这种思维模式的起点仍然是领域场景,上下文相当于是搭建领域场景的舞台。在这个舞台上,进行的并非冷静而细化的过程分解,而是从角色出发,推断和指导参与领域场景的各个演员之间的互动。因此,我们也可以将 DCI 模式结合到场景驱动设计中。</p>
<p>对比场景驱动设计的角色构造型DCI 模式的上下文相当于领域服务数据类相当于聚合。在定义上下文时DCI 模式通过观察不同角色之间的交互来满足领域场景的业务需求。角色方法的定义体现了面向对象“接口隔离原则”与“面向接口设计”的设计思想,而角色之间的交互模式又体现了对象之间良好的行为协作,这在一定程度上保证了领域设计模型的质量,满足可重用性与可扩展性。在上下文之上,是体现了业务价值的领域场景,仍然由应用服务来实现对外业务接口的包装,在内部的实现中,则糅合诸如事务、认证授权、系统日志等横切关注点。至于数据对象的获得,仍然交给资源库。不同之处在于资源库的注入由应用服务来完成,这是因为作为领域服务的上下文协调的是角色之间的交互,即领域服务依赖于角色,而非数据对象的 ID。</p>
<p>结合 DCI 模式的场景驱动设计过程为:</p>
<ul>
<li>识别领域场景,并由对应的应用服务承担</li>
<li>领域场景对应的业务行为由上下文领域服务执行</li>
<li>为了完成该领域场景,明确有哪些角色参与了行为的交互</li>
<li>为这些角色定义角色接口,角色方法实现为默认方法,或者分为抽象与实现</li>
<li>确定承担这些角色的数据对象,定义数据类以及数据类的本地方法</li>
</ul>
<p>即使不遵循 DCI 模式我们也应尽量遵循“角色接口”的设计思想。角色、职责、协作本身就是场景驱动设计分配职责过程的三要素。区别在于二者对角色的定义不同。场景驱动设计的角色构造型属于设计角度的角色定义它来自于职责驱动设计对角色的分类也参考了领域驱动设计的设计模式。不同的角色构造型承担不同的职责但并不包含任何业务含义。DCI 模式的角色是直接参与领域场景的对象,如 Martin Fowler 对角色接口的阐述,他认为是从供应者与消费者之间协作的角度来定义的接口,代表了业务场景中与其他类型协作的角色。</p>
<p>在场景驱动设计过程中,当我们将职责分配给聚合时,可以借鉴 DCI 模式,从领域服务的角度去思考抽象的角色交互,引入的角色接口可以在重用性和扩展性方面改进领域设计模型。当然,这在一定程度上要考究面向对象的设计能力,没有足够的抽象与概括能力,可能难以识别出正确的角色。例如,在薪资管理系统的支付薪资场景中,该为计算薪资上下文引入什么样的角色呢?与转账上下文不同,计算薪资上下文并没有两个不同的角色参与交互,这时的角色就应该体现为<strong>数据类在上下文中的能力</strong>,故而可以获得 PayrollCalculable 角色。数据类 Employee 只有实现了该角色接口,才有“能力”被上下文计算薪资。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997eb3a9f83cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>