领域驱动设计(二)

1.在分析模型中最难做的就是分离领域,如何分离领域?每层之间如何联系?每层做什么?我们为什么使用框架?

    只有分离领域才能将分而治之的思想用到,才能模块化具体化。从抽象概念到领域设计必经之路。

2.基本的模型划分:用户界面层=》应用层=》领域层=》基础设施层

3.面向对象的程序中,常常将业务对象直接写入用户界面、数据库访问等支持代码。而一些业务逻辑则会被嵌入到用户界面组件和数据库脚本中。这么做是为了以最简单的方式在短时间内完成开发工作。

  如果领域相关的代码分散在大量的其他代码中,那么查看和分析领域代码就会变得异常困难。对于用户界面的简单修改实际上很有可能会改变业务逻辑,而要想调整业务规则很有可能需要对用户界面代码、数据库操作代码或者其他程序的元素进行仔细的筛查。这样就不太可能实现一致的、模型驱动的对象了,同时也会给自动化测试带来困难。考虑到程序中各个活动所涉及的大量的逻辑、程序本身必须简单明了,否则让人无法理解。

4.各层直接连接是松散的,层与层之间是单向的。上层可以直接使用或操作下层的元素,方法是通过调用下层元素的接口,保持对下层的引用,以及采用常规的交互手段。而下层元素要和上层元素通信,则需要采用另一种通信机制,使用架构模式来连接上下层,如回调模式或Observers模式。最早将用户界面和领域层和应用层相连接的模式是Model-View-Control.

5.基础设施层不会发起领域层的操作(比如基础的元数据的变化不会影响业务逻辑)。不包含其所服务的领域中的知识,这种需要从领域层访问基础设施通常采用服务的方式。应用层和领域层可以调用基础设施层提供的servers。如果server范围选择合理,接口设计完善,那么通过把详细行为封装到服务接口中,调用程序就可以保持与servers的松散连接,并且自身也会很简单。

6.框架使用,框架的使用是应该明确其使用目的:建立一种可以表达领域模型的实现并且用它来解决重要问题。

7.架构框架和其他工具都在不断发展。新框架越来越多的应用技术问题变的自动化,或者为其提供了预先设定好的解决方案。如果框架使用得当,那么程序开发人员将可以变得更加专注于核心业务问题的建模工作,这会大大提供开发效率和程序质量。不要总是想找着寻找框架,因为精细的框架也可能会束缚住程序开发人员。

8.如果一个经验并不丰富的项目团队要完成一个简单的项目,却决定使用Model-DRIVEN-Design以及Layered  Architecture,那么这个项目组将会经历一个艰难的学习过程。团队成员不得不去掌握复杂的新技术,艰难的学习对象建模。对基础设施和各层管理工作使得原本简单的任务却要花费很长的时间来完成。简单项目的开发周期短,期望值也不是很高。所以,早在项目团队完成任务之前,该项目就会取消,更谈不上去论证有关这种方法的许多令人激动的可行性。

9.在用户界面中实现所有业务逻辑。将应用程序分成小的功能模块,分别将它们实现成用户界面,并在其中嵌入业务规则。用关系数据库作为共享的数据存储库。使用自动化程度最高的用户界面创建工具和可用的可视化编程工具。

二、前面提到了模型,模型到底是什么?模型如何驱动设计?我们怎么建立模型?

前面从知识领域-》UML工具使用-》模型绑定概念-》分层模型的建立。

要想在不削弱模型驱动设计能力的前提下对现实做出一些折中,需要重新组织基本元素。我们需要将模型与实现的各个细节一一联系起来。

从表面上看,定义那些用来捕获领域概念对象很容易,但要反应其含义却很困难。这要求我们明确区分各种模型元素的含义,并与一系列设计实践结合起来,从而开发特定类型的对象。

一个对象是用来表示某种连续性和标识的事物(可以追踪它所经历的不同状态,甚至可以跨不同的实现来跟踪它)还是用来描述属性?这也是我们经常遇到的一个问题到底是写成Entity还是Value Object直接的区别。

从javaee程序开始就有service这一个操作了,但是相信很多人不知道为什么要有service。领域中有一些方面适合用动作或操作来表示,这比用对象表示更加清楚。当软件做的某项无状态的活动进行建模时,就可将该活动作为一项service.

现在我们知道了怎么定义层级中的serverce和Entity那么接下来谈一谈关联:

模型中任何一种可以遍历的关联,软件中都要有同样的属性机制。可以抽象出对象之间的关系,另一方面可以相当于数据库查询的一种封装。在软件中多对多关联应该简化为一对多关联,从而得到一个更加简单的设计。坚持将关联限定为领域所倾向的方向,不仅可以提高这些关联的表达力并简化其实现,而且还可以突出剩下的双向关联的重要性。当双向关联是领域的一个语义特征的时候,或者当应用程序的功能要求双向关联的时候,就需要保留它。

如何简化模型变成一对多的形式呢?

在领域中我们通常关系的是我们的核心业务,通过对业务的核心抽象出发来抽离一对多的模型。

我们来解释一下属性和标识。一个人从出生到死亡总会有一个标识,而这个标识是一直不会变的。但是属性确实不断的发生着变化。那么标识如何找呢?存储形式和真实世界的参与者之间,概念性的标识必须是匹配的。属性可以不匹配,对象建模往往可能会太关注于属性,但实体的基本概念是一种贯穿整个生命周期的抽象的连续性。

一些对象主要不是由他们的属性定义的,它们实际标识了一条"标识线",这条线跨越时间,而且常常经历多种不同的表示。有时,这样的对象必须与另一个具有不同属性的对象相匹配。而有时一个对象必须与具有相同属性的另一个对象区分开。错误的标识可能会破坏数据。

主要由标识定义的对象被称为entity.实体有特殊的建模和设计思路。它们具有生命周期,这期间它们的形式和内容可能发生根本改变,但必须保持一种内在的连续性。为了有效跟踪对象,必须定义它们的标识(hashcode,但我们设计的时候不能将它交给语言来处理)。它们的类定义、职责、属性和关联必须由其标识来决定,而不依赖其所具有的属性。而对于那些不发生根本变化或者生命周期不太复杂的entity,也应该语义上当做entity来对待,这样可以得到更清晰的模型和更加健壮的实现。

entity可以是任何事物,只要满足两个条件即可,一是它在整个生命周期中具有连续性,而是它的区别并不是由那些对用户非常重要的属性决定的。

当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象的定义,使类的定义变的简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方式,这种方式应该与其形式和历史相关。要求格外注意那些需要通过属性来匹配对象的需求。在定义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的符号来实现。这种定义标识的方法可能来自外部,也可能由系统创建的任意标识符,但它在模型中必须是唯一的标识。模型必须定义出"符合什么条件才算是相同的事物"

现在我们有了entity那么接下来就是考虑属性和行为了。但是entity最基本的职责就是连续性(由上下文),以便行为更加清楚可以预测。保持实体的简练是实现这一责任的关键。但是也不要将注意集中到属性和行为上,也要找那些识别、查找、或匹配对象的特征。只添加那些对概念至关重要的行为和这些行为所必须的属性。此外,应该将行为和属性转移到与核心实体关联的其他对象中。这些对象中有可能是entity有可能是value object.实体通过协调其关联的操作来完成自己的职责。

举例:比如一个客户,那么这个是一个entity。因为它有生命周期的连续性和标识。我们可以通过电话号码,地址等信息来找它,这些在entity的生命周期中都是变化的,而且它们没有职责所以只能是属性。

这种标识引出了分布式系统ID的设计。这是分布式一个核心点。

引出一个问题?是不是有关的entity都需要加上标识。

跟踪entity标识是很重要的,但是为其他的对象也加上entity会影响系统性能并增加分析工作,而且会使得模型变得混乱,因为所有对象看起来都是相同的。软件设计要和复杂性做斗争。我们必须区别对待问题,仅在正在需要的地方进行特殊的处理。

然而,如果仅仅将这类对象当做没有标识的对象,那么就忽略他们工具的价值或术语价值。事实上,这些对象有其自己的特征,对模型也有着自己的重要的意义。这些是用来描述事物的对象。

那么地址是一个属性不是一个valueobject,其实也不是????

面向对象精髓在这里,你面向的是什么对象,比如你是根据地址判断客户是否在同一个地方,你的目标是地址,那么它就是你的对象它就是entity.如果你的服务找地址,那么你的服务就是你的对象,地址就是valueobject。

valueobject经常作为参数在对象之间传递消息。当我们只关心一个模型元素的属性的时候,应把它归类为Value Object。我们应该使这个模型元素能够表示出属性的意义,并为它提供相关的功能。valueobject应该是不可变的,不要为它分配任何标识,而且也不要把它设计成同Entity那么复杂。

系统中还需要考虑共享还是复制的问题,一个系统有成千上万个对象。复制对象可能导致系统被大量的对象阻塞,但共享会减慢分布式系统的速度。使用共享的几种情况:

 1.节省数据库空间或减少对象数量是一个关键要求时

2.通信开销很低的时候(如在中央服务器中)

3.共享的对象被严格限定为不可变时

分布式上valueobject关联的时候通常是将对象的副本传递到另外一个服务器上面。

设计包含Value  Object的关联的时候,我们需要完全的清楚valueobject的双向关联,如果你真的需要这样那么你需要一个标识。那就是Serverce

上面总结了如何找entity,主要是生命周期的连续性和标识。一些重要领域的操作无法放到entity和valueObject中。这当中有些操作从本质上讲是一些活动或动作,而不是事物,但是我们建模范式是对象,因此要想办法将它们规划到对象这个范畴里。

我们在用springmvc写业务逻辑的时候常常感觉就是过程性编程,没有体现出面向对象的思想和好处。如果将一个操作放到一个不符合定义的对象中,那么这个对象概念上就会产生混淆,就会变得很难重构和理解。这些也通常以Manager结尾。

1.1一些领域概念不适合被建模为对象。如果勉强把这些重要的领域功能归为Entity或valueObject的职责,那么不是歪曲了基于模型的对象的定义,就是人为增加了一些无意义的对象。

service特征:1.与领域相关的操作不是entity就是valueObject的一个自然组成部分。2.接口是根据领域模型的其他元素定义的。3.操作是无状态的(不会改变属性值不知道谁调用)

当领域中的某一个重要的过程或转化操作不是entity或者valueObject的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为service。记住service是无状态的。service不只是在领域层使用,也要注意区分属于领域层的servcie和其他层的service,并进行职责划分。比如监控程序监控到异常值,就需要发送电子邮件,而封装电子邮件的service接口就是基础设施层面的。

场景举例:现在有一个转账功能的设计,转账设计两个账户的资金的流出和流入。账户是一个entity那么资金转账这个应该属于账户的操作么?是账户这个entity具备的功能么?

不属于,理由:1.这是一个领域的概念,具有全局性(约束性、规范性)。

代码实现:

//概念账户的单一体

public class Account {

    //金额
    private double acct;
    //明细
    private String record;
    //账户名称
    private String acctName;

    public Account() {
    }

    public Account(String acctName) {
        this.acctName = acctName;
    }

    public Account(double acct, String record, String acctName) {
        this.acct = acct;
        this.record = record;
        this.acctName = acctName;
    }

    public  void increaseAcct(double incAcct){
        this.acct+=incAcct;
        System.out.println(acctName+ "账户增加:" +incAcct);

    }

    public boolean reduce(double reduceAcct){
        this.acct-=reduceAcct;
//日志可以通过FundsTransfer类来写
System.out.println(acctName+ "账户扣款:" +reduceAcct);
        return true;
    }

    public double getAcct() {
        return acct;
    }

    public void setAcct(double acct) {
        this.acct = acct;
    }

    public String getRecord() {
        return record;
    }

    public void setRecord(String record) {
        this.record = record;
    }

    public String getAcctName() {
        return acctName;
    }

    public void setAcctName(String acctName) {
        this.acctName = acctName;
    }
}

将概念中的账户抽离出来(账户包含了规则和日志处理等)

public class FundsTransfer{

    //历史记录
    String sigInfo;
    Account sourceAccount;
    Account targetAccount;

    public FundsTransfer( Account sourceAccount,Account targetAccount) {
       this.sourceAccount=sourceAccount;
       this.targetAccount=targetAccount;

    }
    
    public void increaseAcct(double incAcct) {
        sourceAccount.increaseAcct(incAcct);
    }
    
    public boolean reduce(double reduceAcct) {
        if (sourceAccount.getAcct()<reduceAcct){
              return false;
        }else {
            sourceAccount.reduce(reduceAcct);
            targetAccount.increaseAcct(reduceAcct);
            return true;
        }

    }

    public String getSigInfo() {
        return sigInfo;
    }

    public void setSigInfo(String sigInfo) {
        this.sigInfo = sigInfo;
    }
}

//客户与业务交互,service接口

//定义能够为客户做什么,面向的是客户,结果
public class TransferService {

    static Map<String,Account> map=null;

    static {
        map=new HashMap();
        Account accountA=new Account(1000,"2019-02-04","A");
        Account accountB=new Account(0,"2019-02-04","B");
        map.put("A",accountA);
        map.put("B",accountB);

    }
    public  boolean tranfer(String soureAcctName,String targetAcctName,double money){
         //调用转账规则引擎,记住service不包含任何的状态,只是相对于客户的操作
        FundsTransfer fundsTransfer=new FundsTransfer(map.get(soureAcctName),map.get(targetAcctName));
        fundsTransfer.reduce(money);

        return true;
    }

客户端为了更加简单的使用和调度业务系统,将输入资源和业务系统进行解耦。

//外观模式为了简化客户端的调用,封装交互
public class TransferFacade {


    public boolean transfer(String sourceAccountName,String targetAccountName,double money){
        //发送请求
        TransferService service=new TransferService();
        //执行扣钱
        boolean res=service.tranfer(sourceAccountName,targetAccountName,money);
        //成功返回


        return res;
    }

}

客户端调用:

public class MyClient {

    public static void main(String[] args) {
        //拿一个刷卡机
        TransferFacade facade=new TransferFacade();
        //转到B账户
        facade.transfer("A","B",100);
    }
}

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值