领域驱动设计(DDD)前夜:面向对象思想

面向对象

面向对象是一种对世界理解和抽象的方法。那么对象是什么呢?java

对象是对世界的理解和抽象,世界又代称为万物。理解世界是比较复杂的,可是世界又是由事物组成的。git

正是这样的一种关系,认识事物是极其重要的。那什么是事物呢?sql

事物:由两个方面组成。事即事情,物即物体,那什么是事情?什么是物体呢?数据库

  • 意志的行为是为事。
  • 存在的一切是为物,物体又是由属性和行为组成的。

因为对象是对事物的理解和抽象,因此对象就是对一个事物的属性和行为的理解和抽象。正是这样的一种关系,面向对象就是对一个事物的属性和行为的理解和抽象的方法。编程

理解对象以及抽象“对象”就是在理解和抽象事物的属性和行为。服务器

属性和操做

面向对象的核心是对象,对象是由属性方法组合而成的。在使用面向对象进行分析、设计、编码的时候,你首先应该想到的是属性方法组合造成的对象。在须要组合的时候就不该该出现只包含属性的对象或者只包含方法的对象。this

  • 什么时候须要属性和方法组合的对象呢?
  • 什么时候只须要包含属性的对象呢?
  • 什么时候只须要包含方法的对象呢?

事物由事情和物体组成。事情是行为,物体是属性。编码

  • 当你须要抽象一个事物的事情和物体时就须要属性和方法的组合。
  • 当你只须要抽象一个事物的物体时就只须要属性
  • 当你只须要抽象一个事物的事情时就只须要方法

对象建模

在数据库系统中,它们关心的是事物中的物体,因此在抽象事物时它们只抽象了事物中的属性。在应用系统中,它们关心的是表达事物的三种方式(属性和方法的组合、只包含属性、只包含方法),因此在抽象事物时须要思考你须要那种方式。设计

只要须要抽象事物(事情和物体)中的属性,也就是物体的这部分,那有多是须要持久化的。只要须要持久化,一般是保存到关系型数据库中,在关系型数据库中的表(Table)基本上是与面向对象中的对象(Object)的属性是一一对应的。code

因为数据库中的表只抽象了事物中的属性,因此它有多是不完整的。就抽象事物的属性来讲依然有两种:只抽象事物的属性、抽象事物的属性和方法的组合。

正是数据库中的这种抽象造成了数据模型,它对比对象模型是不完整,因此在面向对象分析(OOA)时必定要采用对象(事物)抽象而不是数据(属性、物体)抽象。

举个例子:

简单金融帐户(Account)

属性有:帐号(id)、余额(balance)、状态(status)

操做有:开户(open)、注销(close)、存钱(credit)、取钱(debit)。

数据模型的只须要设计字段(fields)和关联关系,因此下面的 SQL 基本已完成。

create table account
(
    id      integer,
    balance integer,
    status  integer
);

若是把上述 SQL 转换成 Java 的对象的话,获得将是一个用面向对象设计的数据模型,而不是完整的对象模型。这种模型在 Java 开发中很是广泛,这是数据模型思惟所致使的结果。

@Getter
@Setter
public class Account {
    private int id;
    private int balance;
    private AccountStatus status;
}

若是使用对象模型的思惟来设计模型,从接口上来看,他应该是这样的:

public interface Account {

    int getId();

    int getBalance();

    AccountStatus getStatus();

    void open();

    void close();

    void credit(int amount);

    void debit(int amount);
}

若是 Account 接口符合金融帐户的设计,那么 Account 最简单地实现应该以下:

@Getter
public class Account {
    private int id;
    private int balance;
    private AccountStatus status;

    public void open() {
        this.status = AccountStatus.OPENED;
    }

    public void close() {
        this.status = AccountStatus.CLOSED;
    }

    public void credit(int amount) {
        this.balance += amount;
    }

    public void debit(int amount) {
        this.balance -= amount;
    }
}

这是从两个建模的角度来对比对象模型和数据模型的不一样,下面咱们还要从完整地执行流程来对比。

Account Credit

首先是使用数据模型所设计的时序图,由于数据模型下的 Account 不包含业务逻辑,全部的业务逻辑都在 AccountService 中,因此一般称为业务逻辑服务(层)或者事务脚本。如图下:

credit-account-sequence-diagram

使用 Java 代码的实现:

public class AccountService {

    private final AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    public Account creditAccount(int accountId, int amount) {
        var account = this.accountRepository.findById(accountId)
                .orElseThrow(() -> new AccountException("The Account was not found"));
        if (AccountStatus.OPENED != account.getStatus()) {
            throw new AccountException("The Account is not open");
        }
        account.setBalance(account.getBalance() + amount);
        return this.accountRepository.save(account);
    }
}

如今咱们要使用对象模型的思惟进行设计时序图

credit-account-sequence-diagram

使用 Java 代码的实现:

public class AccountService {

    private final AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    public Account creditAccount(int accountId, int amount) {
        var account = this.accountRepository.findById(accountId)
                .orElseThrow(() -> new AccountException("The Account was not found"));
        account.debit(amount);
        return this.accountRepository.save(account);
    }
}

在 AccountService 的 creditAccount 方法中已经没有了业务代码,更多地是协调调用执行流程。对于这种只用来实现执行流程,不在包含业务逻辑的服务对象,将它们称为应用服务(Application Service)。

举个家政服务公司与 AccountService 类似的例子:

好比你想请一位保洁阿姨给家里作一作清洁工做,首先是你打电话给家政服务公司说你要给家里作一作清洁工做,而后家政公司安排一位保洁阿姨去你家帮忙完成清洁工做,在这个过程当中家政公司主要作了接待、协调、安排、最后可能包含一些保洁阿姨的绩效等一系列工做。上面的 AccountService 也同样是在作这样的一件事情。因此在对象模型中,AccountService 只须要作像家政公司这样协调工做,具体地工做由保洁阿姨来完成,这里的保洁阿姨就至关于 Account 对象。

从两处对比来看,采用数据模型建模配合业务逻辑服务的方式更像是过程化编程,只是在使用面向对象语言来编写过程化代码。而采用对象模型配合应用服务的方式才是符合面向对象编程。

组合与聚合

在多数的业务开发中,广泛提到的是关联关系(一对1、一对多、多对多)和继承泛化,不多去关注组合与聚合,可是组合与聚合在面向对象中是至关重要的。

组合与聚合是在探讨总体与部分的关系,这种总体与部分的关系是一种比关联关系更强的关系。好比:汽车与轮胎,汽车是一个总体,轮胎是汽车的一部分。若是汽车没有轮胎,那么就没法构成汽车的完整性,因此在讨论总体与部分的关系时,要特别注意总体对部分的依赖性而不是部分对总体的依赖

首先经过一我的进食过程的用例来考虑总体与部分的依赖关系,而后在例子中说明组合与聚合区别和联系。

这个进食过程须要多我的体器官协做配合。首先是经过一种方式将食物送进口腔,由牙齿的咀嚼和舌头的搅拌,再由喉咙吞咽,从食道进入胃中,在经过胃里进行初步消化,将饮食变成食糜,而后传入小肠后,在脾的运化做用下,精微物质被吸取。

注意:这个从嘴到胃的执行过程并非一个 Input/Output 方式,而是一个 Stream 方式,后面还有链接。从这个角度来考虑嘴只是 Stream 的入口,可是这个用例主要是想说明总体与部分的联系,因此把这种链接的每个部分修改为 Input/Output 调用方式。

为此次进食过程来建模吧!首先肯定关键的对象模型有:人(Person)、嘴(Mouth)、食道(Esophagus)、胃(Stomach)、肠道(Intestine)。代码以下:

// 嘴
public class Mouth {
    public Object chew(Object food) {
        return food;
    }
}

// 食道
public class Esophagus {
    public Object transfer(Object paste) {
        return paste;
    }
}

// 胃
public class Stomach {
    public Object fill(Object paste) {
        return paste;
    }
}

// 肠道
public class Intestine {
    public void absorb(Object chyme) {
        // absorbing...
    }
}

public class Person {
    private final Mouth mouth;
    private final Esophagus esophagus;
    private final Stomach stomach;
    private final Intestine intestine;

    public Person() {
        this.mouth = new Mouth();
        this.esophagus = new Esophagus();
        this.stomach = new Stomach();
        this.intestine = new Intestine();
    }

    public void eat(Object food) { // 进食。
        var paste = this.mouth.chew(food); // 咀嚼造成浆糊。
        paste = this.esophagus.transfer(paste); // 经过食道传送食物。
        var chyme = this.stomach.fill(paste); // 填充到胃里造成食糜。
        this.intestine.absorb(chyme); // 在肠道里吸取养分。
        // 便秘中...
    }
}

public class PersonTests {
    public static void main(String[] args) {
        new Person().eat("chips");
    } 
}

在整个进食流程中,是由人(Person)作的吃(eat)这个动做开始,而后由人体内部的多个参与的部分对象协调完成的,这就是总体与部分的关系。Person 是个总体,Mouth, Esophagus, Stomach, Intestine 是总体内的部分。而后在考虑一个事情,这些部分对象是否是依附在总体对象上,好比:嘴是否是独立于人体不能存活,伴随着人的存在而存在,消亡而消亡。这种部分对象的建立、存在和消亡都是和总体对象一块儿的就称为组合。而聚合就不像组合的总体与部分的关系那么强,好比:汽车与轮胎是一个总体与部分的关系,汽车没有轮胎确定跑不了。可是汽车能够更换轮胎,这种能够更换的关系就没有组合关系那么强。除了更换还有缺乏的,好比:螃蟹有八条腿,总的来讲螃蟹没有腿确定是没法行走的,可是缺乏一个两个仍是能行走的,可能行走有一些困难。这样的能够在初始化以后可以更换的或者不须要强制完整的总体与部分的关系称之为聚合

随着时间的向前和空间的扩大,组合和聚合仍是会存在转换的状况,好比将来人能够换嘴、进食流程不须要嘴的参与,再好比说一次性轿车,出厂后就不能维修更换等等。因此在讨论组合与聚合的关系时,要在必定的限界下来讨论。

开源电商

Mallfoundry 是一个彻底开源的使用 Spring Boot 开发的多商户电商平台。它能够嵌入到已有的 Java 程序中,或者做为服务器、集群、云中的服务运行。

  • 领域模型采用领域驱动设计(DDD)、接口化以及面向对象设计。

项目地址:https://gitee.com/mallfoundry/mall

总结

  • 对象建模,经过对象模型与数据模型的对比来讲明须要一种对象模型的思惟。
  • 对象建模的应用,经过帐户存款的业务来简要说明如何使用对象模型。
  • 组合与聚合,经过重点说明组合与聚合,让其在对象模型的基础上,讨论总体与部分的关系。
相关文章
相关标签/搜索