领域驱动设计(DDD)前夜:面向过程与面向对象思维
面向过程与面向对象思维
在大多数的情况下,我们都是从面向过程的语言(C语言)开始学起编程,然后是进入到面向对象的语言中,比如 Java、C#、Python 等。但在使用面向对象编程时,有可能依然保留着部分面向过程的思维,或者存在一些错误地面向对象思维。
下面我将通过两个示例来对比面向过程与面向对象思维的不同,并在每个示例实现后,再举一个实际示例和错误示例来说明两个问题:
- 在面向对象编程中会存在一些过程化的脚本编码。
- 对象建模中会存在一些对象建模错误问题的。
在描述面向过程与面向对象的区别时,有一个经典的例子叫做《把大象装进冰箱》:
- 人把冰箱门打开
- 人把大象装进去
- 人把冰箱门关上
然而又演变出另外一个版本《把大象走进冰箱》:
- 人把冰箱门打开
- 大象走进冰箱
- 人把冰箱门关上
这两个版本一个是把大象装进冰箱,另一个是大象自己走进冰箱。在使用面向过程和面向对象实现这两个用例时,你应该全部实现出来,也就是说应该使用面向过程分别实现这两个用例:装进冰箱和走进冰箱,使用面向对象也分别实现这两个用例:装进冰箱和走进冰箱。然后再去横向对比,使用面向过程实现的“把大象装进冰箱”与使用面向对象实现的“大象装进冰箱”。而不是使用面向过程实现装进冰箱,使用面向对象实现走进冰箱,然后交叉对比。如下表格:
面向过程 | 面向对象 |
---|---|
装进冰箱 | 装进冰箱 |
走进冰箱 | 走进冰箱 |
把大象装进冰箱
首先来实现把大象装进冰箱的面向过程示例:
// 定义一个用于操作数组的 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) }
其中使用Elephant
和Fridge
对象来模拟结构体,然后使用打开(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() }
就这两个示例好像是对比说明了面向过程与面向对象的一些差别。但这两种思维对日常工作能有什么影响和帮助呢?
比如现在要开发一个智能家居系统,其中有一个功能:“智能管家”可以将一些水果放置到冰箱里,并在操作完成后,智能管家通过调用后端服务接口来及时更新冰箱信息,然后房主就可以通过手机查看到冰箱内的物品。现在要将“智能家居系统”设计成一个后端服务,这样就可以为智能管家和手机终端提供服务。
使用面向过程的实现:
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 编程思想》说的那样“如果说它有缺点,那就是掌握它需付出的代价。”
大象走进冰箱
使用面向过程的实现:
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()
)来引起大家对对象建模深层思考。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
领域驱动设计(DDD):对象属性(property)和 getters , setters 方法
对象属性(property)和 getters , setters 方法 “需要为一个对象的属性添加 Getters / Setters 方法”而提出为什么?由此而进行深入思考。 它是字段(field) 在 Java 中我们都知道如何在类(Class)中声明一个成员属性(field)。 public class HikariConfig { public long connectionTimeout; public long validationTimeout; } 当我们需要设置对象的属性值时,我们可以直接使用 = 赋值。 public class HikariConfigTests { public static void main(String[] args) { var config = new HikariConfig(); config.connectionTimeout = 250; config.validationTimeout = 250; } } 如果我们需要在设置 connectionTimeout 属性时,做一些赋值校验。比如:connectionTimeou...
- 下一篇
为什么要有 Servlet ,什么是 Servlet 容器,什么是 Web 容器?
微信搜 「yes的练级攻略」干货满满,不然来掐我,回复【123】一份20W字的算法刷题笔记等你来领。 个人文章汇总:https://github.com/yessimida/yes 欢迎 star ! 以下代码相信大家都很熟悉,大学时学 Java Web 都写过这样的代码。 从第一次接触 Servlet 到之后的很长一段时间内,我都没理解 Servlet 是个什么玩意? 为什么要有 Servlet ? 为什么要有 Servlet 容器? 啥又是 Web 容器、HTTP 服务器? 今儿咱们就来盘盘,并且从中来看看架构和框架的设计套路。 看完之后可能对接口、抽象会有进一步的认识。 来,上车! 正文 首先浏览器发起 HTTP 请求,像早期的时候只会请求一些静态资源,这时候需要一个服务器来处理 HTTP 请求,并且将相应的静态资源返回。 这个服务器叫 HTTP 服务器。 简单点说就是解析请求,然后得知需要服务器上面哪个文件夹下哪个名字的静态文件,找到返回即可。 而随着互联网的发展,交互越发得重要,单纯的静态文件满足不了需求。 业务变得复杂,需要我们编写代码来处理诸多业务。 需要根据 HTTP ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Mario游戏-低调大师作品
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果