領域驅動設計(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

總結

  • 對象建模,經過對象模型與數據模型的對比來講明須要一種對象模型的思惟。
  • 對象建模的應用,經過帳戶存款的業務來簡要說明如何使用對象模型。
  • 組合與聚合,經過重點說明組合與聚合,讓其在對象模型的基礎上,討論總體與部分的關係。
相關文章
相關標籤/搜索