领域模型和领域对象的概念

面向对象架构模式之:领域模型(Domain Model)

领域模型是对领域内的概念类或现实世界中对象的可视化表示。又称概念模型、领域对象模型、分析对象模型。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。

 
    面向领域对象设计的原则简单的说就是起名字定职能,对象定领域,找关系做关联。俗话:起名字,画圈,画线。
    领域模型是为了解决问题,所谓领域就对应一个问题或者主题。
    领域解决了再解决表述和表述持久化的问题。
    

先来完成最简单的部分,即找关系。也就是说,按照所谓的关系,我们来重构 事务脚本 中的代码。上篇“你在用什么思想编码:事务脚本 OR 面向对象?”中同样的需求,如果用领域模式来做的话,我们大概可以这样设计:

image

概念

编辑
业务对象模型(也叫领域模型 domain model)是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。业务对象模型从业务角色内部的观点定义了业务用例。该模型为产生预期效果确定了业务人员以及他们处理和使用的对象(“业务类和对象”)之间应该具有的静态和动态关系。它注重业务中承担的角色及其当前职责。这些模型类的对象组合在一起可以执行所有的业务用例。

核心元素

编辑
业务角色显示了一个人承担的一系列职责。业务实体表示使用或产生的可交付工件、资源和事件。业务用例实现显示了协作的业务角色和业务实体如何执行某个工作流程。使用以下几种图来记录业务用例实现: 图显示参与的业务角色和业务实体。活动图,其中泳道显示业务角色的职责,而对象流显示如何在工作流程中使用业务实体。 序列图描述业务角色和业务主角之间交互的详细情况,并显示如何在业务用例执行过程中访问业务实体。
业务对象模型将结构的概念和行为的概念结合了起来。
它是一个纽带工件,用于对业务关系进行清晰的表述,表述方式与软件开发人员的思考方式类似,同时仍保留一些纯粹的业务内容。将我们所知道的有关业务的信息按照对象、属性和职责进行了合并。
它探索业务领域知识的本质,所采用的方式使我们能够从对业务问题的思考转变到对软件应用程序的思考上来。
它是一种确定需求的方法,使需求能够为待建信息系统使用,并得到该系统的支持。
确定业务对象定义、对象间关系、对象名称和对象间关系名称的流程使我们能够以一种能被业务领域专家理解和验证的精确方式来表达业务领域知识。
领域模型领域模型

命名

编辑
对每个业务角色和实体进行命名,要求名称能够表示对象的职责。
一个好的名称通常是名词或动词的名词形式, 每个名称都必须是唯一的。避免使用发音或拼写类似的词以及同义词作为名称,可能需要用好几个单词来组成一个明确的、无需额外说明的名称。

对象

编辑
当您研究参与业务中不同用例的业务角色和业务实体时,可能会发现某些对象如此相似,以致于实际上是一个类。即使不同的业务用例没有相同的要求,类是之间也可能相似到足以被视为一个相同现象的程度。如果是这种情况,您应该将相似的类合并在一起。这就产生了一个业务角色或业务实体,它拥有足以满足不同业务用例要求的关系、属性和操作。
因此,多个业务用例可以对同一个类有不同的要求。对于业务角色来说,如果有些雇员有能力担当所描述的一组角色,那么同样还要有一些比较灵活可以胜任多个职位的雇员。这会使您的业务更加灵活。

模型

编辑
在业务对象模型中,业务角色代表雇员将担当的角色,而业务实体则代表雇员将处理的对象。一方面,可以使用业务对象模型来确定业务雇员将如何进行交互,以产生业务主角所期望的结果。另一方面,系统用例模型和设计模型指定了业务的信息系统。
业务建模和系统建模解决不同的问题,其抽象程度也不一样。所以一般而言,信息系统不应该直接出现在业务模型中。
另一方面,雇员作为业务角色来使用信息系统,实现相互之间的通信、与主角的通信以及对业务实体信息进行访问。所有的链接、关联关系或属性都有某个潜在的信息系统对其进行支持。
这两类建模环境有以下关系:
作为特定业务角色的雇员与信息系统的一个系统主角相对应。如果建立的信息系统使该雇员在业务用例中的所有工作都得到一个系统用例的支持,则他最有可能得到最好的支持。 另外,如果业务用例规模大、生存期长或者合并了多个独立领域中的工作,信息系统用例将可以支持业务角色的操作。 雇员工作的对象(建模为业务实体)常在信息系统中得到表现。在信息系统的对象模型中,这些业务实体作为实体类出现。业务实体之间的关联关系和聚合关系常常使设计模型中实体类之间产生对应的关联关系和聚合关系。 因此,系统用例访问并操作设计模型中的实体类,这些实体类代表由被支持业务用例访问的业务实体。最后,直接使用业务信息系统的业务主角也成为信息系统的系统主角。 当确定对支持业务的信息系统的需求时,这些关系十分关键。

主角

编辑
有时候,一个业务的雇员与另一个业务的雇员使用其他业务的信息系统进行联系。从建模后业务的角度来看,这个信息系统就是一个业务主角。
示例: 某个软件开发人员努力去理解他所负责的产品中出现的问题。为了了解问题是否源于他所使用的编程工具,他与供应商的万维网服务器联系,并仔细研究编程工具当前版本中已知问题的列表。通过这种方式,业务角色“软件开发人员”与业务角色“提供商的万维网服务器”进行交互。

定位

编辑
通常的做法是不在业务对象模型中对信息系统进行明确建模,因为信息系统只是业务角色所使用的工具而已。但当业务的信息系统被客户直接使用时,这种做法就不合适了。如果这个交互是业务服务的主要部分,您可能会出于商业上重要性的考虑而希望在业务对象模型中将其展示出来。电话银行业务就是此类信息系统的一个很好的例子。
从业务建模的观点来看,建议使用以下方法:
将信息系统看做一个和主角交互的完全自动化的业务角色。如果信息系统和任何其他业务角色或业务实体相关,则考虑使用链接或关联关系来说明这种关系。系统可能会向某个业务角色通知其进度,或者使用与某个业务实体相关的信息。 简单地说明业务角色,同时列出代表业务对象模型中信息系统的服务。在信息系统模型中对信息系统和其环境的所有细节和特征进行建模。引入一个命名约定,这样可以容易地在业务角色中确定那些完全自动化的业务角色,例如,一个前缀或后缀,如“自动<业务角色名称>”或“<业务角色名称>(IT 系统)”。您甚至可以使用一个特殊的图标来定义构造型。

特征

编辑
总的来看,业务角色和业务实体执行业务用例中描述的所有活动,绝不多一点,也绝不少一点。业务对象模型有效、全面地对组织进行了展示。

设计

编辑
举一个简单的例子来说明如何进行领域模型设计。
假如我们要为一个小卖店设计一套进销存系统,她为我们提供的业务描述是这样的:每天凌晨从布吉农批市场买苹果、梨、葡萄、橘子、香蕉、荔枝、核桃等等,反正哪些好卖她就买回来卖。葡萄、荔枝不能长久保留,一般要当天卖出去…。
针对上面这段业务描述,我们怎么进行领域模型设计?我给出以下几个步骤来完成领域模型设计。
总结业务描述中的名词
首先建一个名词表,把涉及到的名词列出来:
序号名词备注;
1. 布吉农批市场
2. 买东西的人是一个隐含的名词,每天凌晨从农批市场拿货
3. 苹果
4. 梨
5. 葡萄
6. 橘子
7. 香蕉
8. 荔枝
9. 核桃
10. 顾客是一个隐含的名词,买回来卖的对象
11. 凌晨、当天时间名词,与实体及角色无关
这个名词列表包括了业务的行为主体:角色,以及业务过程中的操作实体:模型,对我们接下来的用例描述、领域模型分析、需求分析很有帮助。当然这个名词列表需要经过进一步分析提炼,成为领域模型
确定业务实体
序号名词描述;
1. 布吉农批市场不是本业务的一个实体
2. 买东西的人是本业务的一个角色
3. 苹果是一个实体
4. 梨是一个实体
5. 葡萄是一个实体
6.橘子是一个实体
7. 香蕉是一个实体
8. 荔枝是一个实体
9. 核桃是一个实体
10. 顾客是本业务的一个角色
11. 凌晨、当天时间名词,与实体及角色无关

抽象业务模型

编辑
经过分析,我们得出的实体是苹果、梨、葡萄、橘子、香蕉、荔枝、核桃,这些是不是模型呢?应该说还不是,还要经过进一步分析:在我们分析的业务领域内,它们有没有共性?苹果、梨、葡萄、橘子、香蕉、荔枝属于水果,核桃属于干果,它们都是果品的一个具体实例。而在水果中葡萄和荔枝属于不宜保存水果,通过这样进一步的分析得出如下的领域模型:
果品进销存领域模型
这个领域模型不但能反映当前的经营实体,同时给我们需求分析人员和系统功能提供了一定的扩展视野:将来会不会经营食品,短期保持水果采取什么利润空间来促销,长期保存的水果会不会因为保存成本而导致利润下降。

关系

编辑
认为领域模型它是一个分析模型,帮助系统分析人员、用户认识现实业务的工具,描述的是业务中涉及到的实体及其相互之间的关系,它是需求分析的产物,与问题领域相关。领域模型是需求分析人员与用户交流的有力工具,是需求分析人员与用户共同理解的概念,是彼此之间交流的语言。而数据模型是系统设计、实现的一部分,描述的是对用户需求在数据结构上的实现,仅此而已。当然数据模型中的概念模型设计与领域模型类似,缺乏的是实体之间更广泛的关系描述。
通常大家会考虑数据怎么存放的问题,我的理解是领域模型设计期间不用考虑数据的存放问题,只考虑业务描述中涉及的实体以及实体之间的关系。
实体之间的关系,很多书都讲了,无非是泛化、依赖和关联,关联又分了一般关联、聚合、组合等等,我这里就不列了。

总结

编辑
领域模型设计是需求分析的关键步骤。它帮助用户及需求分析人员建立业务概念,确定用户业务的问题域,系统涉及的业务范围等等。
领域模型设计的步骤为:
1. 从业务描述中提取名词;
2. 从提取出来的名词中总结业务实体,区分名词中的属性、角色、实体、实例,形成问题域中操作实体的集合;
3. 从业务实体集合中抽象业务模型,建立问题域的概念(例如在前面的例子中,我们把容易变质的水果称之为“短期保持水果”,当然也可以是其它说法,只要能跟用户达成共识即可);
4. 用UML提供的方法和图例进行领域模型设计、确定模型之间的关系;

领域对象驱动开发:来吧,让我们从对象开始吧

 今天我们通过一个“超市收银”业务来作为我们的示例(虽然这个示例看上去不太正常,但是它确表述我们所需要的)。我们将从业务分析到业务建模然后最后的编码来用“面向领域对象”的方式来做我们的项目。

好,我们开始吧!

 

一、业务分析

 

大家都去超市买过东西,对超市收银业务都比较熟悉。什么?你不熟?好吧,那我们找个收银员给大家讲解下(领域专家)。

 

收银员小慧:哦,是这样呢。顾客排队银帐我就收银呢,我要使用收银机呢。收银机就能计算出要收的钱呢,我就扫一下呢,就OK了呢。然后就收银了呢。

 

听了小慧的讲解,我们心中有了业务的概念了。我们这里采用《业务关键字分析法》来找出此业务流程里面的一些关系字:

 

商品

顾客

收银员

收银机

*收银

*选商品

*收银员使用收银机

*收银机扫商品计算金额

 

好了,列出这些“业务关键字”了,我们就可以建我们的对象模型了。

 

二、系统建模

 

上面我们分析出了一些“业务关键字”接下来我们分析这些业务关键字并深入他们的业务。

 

商品对象(Goods)

属性:商品名称(GoodsName)、商品价格(GoodsPrice)

行为:在这里商品对象是没有行为的,我们也可以叫它“值对象”。

 

顾客对象(Customer)

属性:顾客姓名(CustomerName)、顾客选购的商品(Goodss)

行为:选购想买的商品(LikeBuy)、听收银员说要收多少RMB(ListenAmount)

 

收银员对象(Cashier)

属性:收银员姓名(CashierName)

行为:收银(CashierRegister)

 

收银机对象(CashierRegister)

属性:收银机编号(CashRegisterNo)

字段:总金额(_totalAmount)

行为:收银(CashRegisters)、显示收银总额(ShowAmount)

 

 

有木有!有木有?有木有很直观,这也就是面向对象分析的好处,因为对象就是对现实的抽象,我们现实中的事务可以很方便的用对象抽象出来。我们很容易发现,这和用表来描述这些业务模型显然要不方便的多。表还只能描述属性,造成了属性与行为的分离。

 

三、代码示例

 

商品对象

 

复制代码
/// <summary>
    
/// 商品
    
/// </summary>
    public class Goods
    {
        
/// <summary>
        
/// 对象标识
        
/// </summary>
        public Guid OKey { getset; }
        
/// <summary>
        
/// 商品名称
        
/// </summary>
        public string GoodsName { getset; }
        
/// <summary>
        
/// 商品价格
        
/// </summary>
        public decimal GoodsPrice { getset; }
    }
复制代码

 

顾客对象

 

复制代码
/// <summary>
    
/// 顾客
    
/// </summary>
    public class Customer
    {
        
/// <summary>
        
/// 对象标识
        
/// </summary>
        public Guid OKey { getset; }
        
/// <summary>
        
/// 顾客姓名
        
/// </summary>
        public string CustomerName { getset; }

        
private List<Goods> _goodss = new List<Goods>();
        
/// <summary>
        
/// 顾客购买的商品
        
/// </summary>
        public List<Goods> Goodss
        {
            
get { return _goodss; }
            
set { _goodss = value; }
        }

        
/// <summary>
        
/// 顾客选购商品
        
/// </summary>
        
/// <param name="goods">商品</param>
        public void LikeBuy(Goods goods)
        {
            
this._goodss.Add(goods);
        }

        
/// <summary>
        
/// 听收银员应收多少钱
        
/// </summary>
        
/// <param name="amount"></param>
        public void ListenAmount(decimal amount)
        {
            Console.WriteLine(
"我是[{0}],我买了{1}件商品。我共花了{2}元RMB。"this.CustomerName, this.Goodss.Count, amount.ToString("f2"));
        }
复制代码

 

收银员对象

 

复制代码
/// <summary>
    
/// 收银员
    
/// </summary>
    public class Cashier
    {
        
/// <summary>
        
/// 对象标识
        
/// </summary>
        public Guid OKey { getset; }
        
/// <summary>
        
/// 收银员姓名
        
/// </summary>
        public string CashierName { getset; }

        
/// <summary>
        
/// 收银
        
/// </summary>
        
/// <param name="customer">顾客</param>
        public void CashRegister(Customer customer)
        {
            
//打开使用收银机
            CashRegister cashRegister = new CashRegister();

            
//对顾客的商品进行收银机扫码,收银
            foreach (var goods in customer.Goodss)
            {
                
//使用收银机扫商品进行收银
                cashRegister.CashRegisters(goods);
            }

            
//通知顾客一共收多少钱
            customer.ListenAmount(cashRegister.ShowAmount());
        }
    }
复制代码

 

收银机对象

 

复制代码
/// <summary>
        
/// 对象标识
        
/// </summary>
        public Guid OKey { getset; }
        
/// <summary>
        
/// 收银机编号
        
/// </summary>
        public string CashRegisterNo { getset; }

        
/// <summary>
        
/// 总价格
        
/// </summary>
        private decimal _totalAmount { getset; }

        
public CashRegister()
        {
            
//收银总额置0
            this._totalAmount = 0;
        }

        
/// <summary>
        
/// 收银
        
/// </summary>
        
/// <param name="goods">商品</param>
        public void CashRegisters(Goods goods)
        {
            
this._totalAmount += goods.GoodsPrice;
        }

        
/// <summary>
        
/// 显示收银总额
        
/// </summary>
        
/// <returns></returns>
        public decimal ShowAmount()
        {
            
return this._totalAmount;
        }
复制代码

 

模拟业务流程

 

复制代码
//我们创建几样商品
            Goods RedWine = new Goods() { GoodsName = "红酒", GoodsPrice = 1800,OKey=Guid.NewGuid() };
            Goods Condoms 
= new Goods() { GoodsName = "安全套", GoodsPrice = 35,OKey=Guid.NewGuid() };

            
//我们创建几位顾客
            Customer Chunge = new Customer() { CustomerName = "春哥", OKey = Guid.NewGuid() };
            Customer Beianqi 
= new Customer() { CustomerName = "贝安琪", OKey = Guid.NewGuid() };

            
//当然,我们需要收银员啊
            Cashier CashierMM = new Cashier() { CashierName = "收银员MM", OKey = Guid.NewGuid() };

            
//顾客逛了一圈,选了自己想要的商品
            Chunge.LikeBuy(RedWine);
            Beianqi.LikeBuy(RedWine);
            Beianqi.LikeBuy(Condoms);

            
//顾客开始排队结帐了
            Queue<Customer> customerQueue = new Queue<Customer>();
            customerQueue.Enqueue(Chunge);
            customerQueue.Enqueue(Beianqi);

            
//队伍过来,按先后顺序挨个收银喽
            foreach (var customer in customerQueue)
            {
                
//收银
                CashierMM.CashRegister(customer);
            }
复制代码

 

显示结果

 

 

上面的例子虽然不是很恰当,但是它也很好的像我们表达出了领域驱动分析问题、面向对象驱动开发的好处了。

 

最后大家回想一下,用数据库表驱动的方式。分析这个业务会是什么样子的。。。。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页