领域驱动设计(DDD)前夜:面向过程与面向对象思惟

面向过程与面向对象思惟

在大多数的状况下,咱们都是从面向过程的语言(C语言)开始学起编程,而后是进入到面向对象的语言中,好比 Java、C#、Python 等。但在使用面向对象编程时,有可能依然保留着部分面向过程的思惟,或者存在一些错误地面向对象思惟。git

下面我将经过两个示例来对比面向过程与面向对象思惟的不一样,并在每一个示例实现后,再举一个实际示例和错误示例来讲明两个问题:typescript

  • 在面向对象编程中会存在一些过程化的脚本编码。
  • 对象建模中会存在一些对象建模错误问题的。

在描述面向过程与面向对象的区别时,有一个经典的例子叫作《把大象装进冰箱》:编程

  1. 人把冰箱门打开
  2. 人把大象装进去
  3. 人把冰箱门关上

然而又演变出另一个版本《把大象走进冰箱》:后端

  1. 人把冰箱门打开
  2. 大象走进冰箱
  3. 人把冰箱门关上

这两个版本一个是把大象装进冰箱,另外一个是大象本身走进冰箱。在使用面向过程和面向对象实现这两个用例时,你应该所有实现出来,也就是说应该使用面向过程分别实现这两个用例:装进冰箱和走进冰箱,使用面向对象也分别实现这两个用例:装进冰箱和走进冰箱。而后再去横向对比,使用面向过程实现的“把大象装进冰箱”与使用面向对象实现的“大象装进冰箱”。而不是使用面向过程实现装进冰箱,使用面向对象实现走进冰箱,而后交叉对比。以下表格:数组

面向过程 面向对象
装进冰箱 装进冰箱
走进冰箱 走进冰箱

把大象装进冰箱

首先来实现把大象装进冰箱的面向过程示例:服务器

// 定义一个用于操做数组的 push 方法。
declare function push(array: any[], element: any)

class Elephant {
}

class Fridge {
   public status: string
   public elephants: Elephant[] = []
}

function open(fridge: Fridge) {
    fridge.status = "opened"
}

function put(fridge: Fridge, elephant: Elephant) {
    push(fridge.elephants, elephant)
}

function close(fridge: Fridge) {
    fridge.status = "closed"
}

function main() {
    const elephant = new Elephant()
    const fridge = new Fridge()
    open(fridge)
    put(fridge, elephant)
    close(fridge)
}

其中使用ElephantFridge对象来模拟结构体,而后使用打开(open)、放进(put)、关闭(close) 这三个函数来分别完成对应的业务逻辑。编程语言

而后是把大象放进冰箱的面向对象的实现示例:函数

class Elephant {
}

class Fridge {
    status: string
    elephants: Elephant[] = []

    open() {
        this.status = "opened"
    }

    put(elephant: Elephant) {
        this.elephants.push(elephant)
    }

    close() {
        this.status = "closed"
    }
}

function main() {
    const elephant = new Elephant() 
    const fridge = new Fridge()
    fridge.open()
    fridge.put(elephant)
    fridge.close()
}

就这两个示例好像是对比说明了面向过程与面向对象的一些差异。但这两种思惟对平常工做能有什么影响和帮助呢?学习

好比如今要开发一个智能家居系统,其中有一个功能:“智能管家”能够将一些水果放置到冰箱里,并在操做完成后,智能管家经过调用后端服务接口来及时更新冰箱信息,而后房主就能够经过手机查看到冰箱内的物品。如今要将“智能家居系统”设计成一个后端服务,这样就能够为智能管家和手机终端提供服务。ui

使用面向过程的实现:

class FridgeService {

    private readonly fridgeRepository: FridgeRepository

    constructor(fridgeRepository: FridgeRepository) {
        this.fridgeRepository = fridgeRepository
    }

    // /v1/fruits/:fridgeId/open
    public openFridge(fridgeId: string): Fridge {
        const fridge = this.fridgeRepository.findById(fridgeId)
        fridge.status = "opened"
        return this.fridgeRepository.save(fridge)
    }

    // /v1/fruits/:fridgeId/put
    public putFruit(fridgeId: string, fruit: Fruit): Fridge {
        const fridge = this.fridgeRepository.findById(fridgeId)
        if (fridge.status !== "opened") {
            throw new Error("The fridge is not open")
        }
        fridge.fruits.push(fruit)
        return this.fridgeRepository.save(fridge)
    }
}

这应该是你们最多见的编码方式,直接在 Service 里面编写业务逻辑,但这并既不是一个好地面向过程编码规则也不是一个好地面向对象的编码规则。首先你没有像面向过程那样将业务封装成一个个功能函数,也没有像面向对象那样将业务封装到实体对象的方法内,这只能算是脚本化开发

使用面向对象的实现:

class Fridge {
    status: string
    fruits: Fruit[] = []

    open() {
         this.status == "opened"
    }

    isOpened() {
        return this.status === "opened"
    }

    put(fruit: Fruit) {
        if (!this.isOpened()) {
            throw new Error("The fridge is not open")
        }
        this.fruits.push(fruit)
    }
}

class FridgeService {

    // /v1/fruits/:fridgeId/open
    public openFridge(fridgeId: string): Fridge {
        const fridge = this.fridgeRepository.findById(fridgeId)
        fridge.open() // Open fridge
        return this.fridgeRepository.save(fridge)
    }

    // /v1/fruits/:fridgeId/put
    public putFruit(fridgeId: string, fruit: Fruit): Fridge {
        const fridge = this.fridgeRepository.findById(fridgeId)
        fridge.put(fruit) // Put fruit
        return this.fridgeRepository.save(fridge)
    }
}

从最开始的将“大象装进冰箱”的两种实现和“智能家居系统”的两种实现,实际上是三种编程方式:具备小函数思惟的面向过程、脚本化的面向过程以及面向对象的编程方式。

经过这三种方式的对比,咱们应该发现到脚本化开发是最简单的,学习成本最低而且最多见的。可是这种模式所开发的项目通过日积月累之后会变的难以维护。他几乎没有功能抽象性,只是对一个功能进行逻辑实现,好一些的脚本代码会对一个功能进行分解,但尚未达到像“大象装进冰箱”的面向过程示例那样,将一个业务功能细化的分解成:开门(open)、放进去(put)、关门(close)这些功能函数。一旦细化后那他就是具备小函数思惟的面向过程。若是你如今是使用的像 Java、C# 这样的纯面向对象非多范式的编程语言,这两种思惟你都不适合,你应该在面向对象的编程语言里使用面向对象的思惟进行编码,而不是在面向对象里留恋面向过程。在培养面向对象思惟时,就像《Java 编程思想》说的那样“若是说它有缺点,那就是掌握它需付出的代价。”

tij-1

大象走进冰箱

使用面向过程的实现:

function into(elephant: Elephant, fridge: Fridge) {
    push(fridge.elephants, elephant)
}

function main() {
    const elephant = new Elephant()
    const fridge = new Fridge()
    open(fridge)
    into(elephant, fridge) // 大象走进冰箱
    close(fridge)
}

这个对比“把大象装进冰箱的面向过程的实现”来看,区别不大。只是 put(fridge,elephant) 方法改成了 into(elephant, fridge) 方法,但这形参位置一前一后的改变是编程语言与天然语言的顺序描述,或者是主动被动的区别,主动被动,在软件体系结构中主动被动是一个重要概念。

使用面向对象的实现:

class Elephant {
    into(fridge: Fridge) {
        fridge.put(this)
    }
}

function main() {
    const elephant = new Elephant()
    const fridge = new Fridge()
    fridge.open()
    elephant.into(fridge) // 大象本身走进冰箱。
    fridge.close()
}

“大象走进冰箱”这样的用例在现实业务中仍是有的,好比说:智能汽车自动入库,当告诉汽车须要驶入的车库后,智能汽车能够自动完成入库操做。

可是有一些在编写“大象走进冰箱”的代码实现上有一些错误。好比:

const fridge = new Fridge()
    const elephant = new Elephant()
    fridge.open()
    elephant.into() // Error
    fridge.close()

其中最大地问题在于 elephant.into() 这个方法调用。能够看到 into() 是个空参方法,这样与 fridge 对象就毫无关系,没有任何关系大象是走去哪呢?因此这个实如今严谨性和思惟上有些失误。

开源电商

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

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

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

总结

首先是经过“大象装进冰箱”的示例来讲明面向过程与面向对象的区别,而后又经过“智能家居系统”的示例来讲明在面向对象编程中可能存在着一些脚本化编码的问题。在“大象装进冰箱”和“智能家居系统”两个示例中逐步引导使用面向对象来思考问题。

其次又经过“大象走进冰箱”的示例来讲明的被动与主动关系,并在最后经过错误调用(elephant.into())来引发你们对对象建模深层思考。

相关文章
相关标签/搜索