设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17606 人已学习
课程目录
已更新 21 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (4讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?

王争 2019-11-27
在前面几节课中,我们学习了面向对象的一些理论知识,比如,面向对象四大特性、接口和抽象类、面向对象和面向过程编程风格、基于接口而非实现编程和多用组合少用继承设计思想等等。接下来,我们再用四节课的时间,通过两个更加贴近实战的项目来进一步学习,如何将这些理论应用到实际的软件开发中。
据我了解,大部分工程师都是做业务开发的,所以,今天我们讲的这个实战项目也是一个典型的业务系统开发案例。我们都知道,很多业务系统都是基于 MVC 三层架构来开发的。实际上,更确切点讲,这是一种基于贫血模型的 MVC 三层架构开发模式。
虽然这种开发模式已经成为标准的 Web 项目的开发模式,但它却违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格,因此而被有些人称为反模式(anti-pattern)。特别是领域驱动设计(Domain Driven Design,简称 DDD)盛行之后,这种基于贫血模型的传统的开发模式就更加被人诟病。而基于充血模型的 DDD 开发模式越来越被人提倡。所以,我打算用两节课的时间,结合一个虚拟钱包系统的开发案例,带你彻底弄清楚这两种开发模式。
考虑到你有可能不太了解我刚刚提到的这几个概念,所以,在正式进入实战项目的讲解之前,我先带你搞清楚下面几个问题:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(143)

  • ## 为什么贫血模型盛行

    下面几项自己都中过招(环境问题和个人问题):

    ### 环境问题 ##

    * 近朱者赤,近墨者黑
        * 大多数人都是模仿别人的代码,而别人的代码基本上都是 demo,没有复杂的业务逻辑,基本是贫血模型
        * 找不到好的指导与学习对象
    * 接触不到复杂业务项目
        * 做 web 项目的,很大一部分就是简单的 CURD,贫血模型就能解决
    * 公司以任务数来衡量个人价值

    ### 个人问题 ###

    * 不考虑项目质量属性
        * 只关心当前业务,没有意识去思考后期该如何维护和响应业务变更
    * 求快不求质
        * 个人以任务数来自我满足
        * 没有 60 分和 100 分的概念
        * 需求分析、设计、编码合为一体

    ## 如何理解充血模型

    先推荐一本书:整洁架构设计

    先说一下充血模型中各组件的角色:

    * controller 主要服务于非业务功能,比如说数据验证
    * service 服务于 use case,负责的是业务流程与对应规则
    * Domain 服务于核心业务逻辑和核心业务数据
    * rep 用于与外部交互数据

    ----

    额外说一点,业务开发个人倾向于六边形架构,而非传统的三层架构。六边形架构更能体现当下 web 应用的场景

    六边形项目结构(根据实际情况自行组织与定义):

    * InboundHandler 代替 controller
        * *WebController:处理 web 接口
        * *WeChatController:处理微信公众号接口
        * *AppController:处理 app 接口
        * *MqListener:处理 消息
        * *RpcController:处理子系统间的调用
    * service 服务于 use case,负责的是业务流程与对应规则
        * CQPS + SRP:读写分离和单一原则将 use case 分散到不同的 service 中,避免一个巨大的 service 类(碰到过 8000 行的 service)
    * Domain 服务于核心业务逻辑和核心业务数据
        * 最高层组件,不会依赖底层组件
        * 易测试
    * outBoundhandle 代替 rep
        * MqProducer:发布消息
        * Cache:从缓存获取数据
        * sql:从数据库获取数据
        * Rpc:从子系统获取数据

    ----

    各层之间的数据模型不要共用,主要是因为稳定性不同,各层数据模型的变更原因和变更速率是不同的,离 IO 设备越近的的稳定性越差,比如说 controller 层的 VO,rep 层的 entity。Domain 层是核心业务逻辑和核心业务数据,稳定性是最高的

    ----

    几个不太容易理解的点(我刚开始碰到的时候很费解):

    * use case 和 核心业务逻辑该如何定义与区分
        * 哪些该放到 service 里面,哪些该放到 Domain 中
    * rep 是依赖于 service 的,而不是 service 依赖 rep 层
        * 业务逻辑是最高层组件(最稳定的),rep 层是底层组件
    * 接口能反转依赖关系

    ----

    一剂良药:所有的中间层都是为了解耦
    2019-11-27
    11
    79
  • 业余爱好者
    一直贫血而不自知
    2019-11-27
    5
    66
  • 有铭
    我个人认为,充血模型在web开发领域不流行的一个根本原因,在于互联网兴起后各种层出不穷的需求变动,以及短命的项目生存周期,充血模型应对复杂业务确实很有优势,但是这是建立在复杂业务本身其实相对稳定上,比如银行的业务,虽然复杂,但是其实很稳定。但是要是换在互联网,今天改需求明天改需求,甚至很多时候根本就是推倒了重来的需求,充血模型面对这种状态,根本是力不从心的
    2019-11-27
    5
    23
  • 李小四
    设计模式_10
    # 问题:
    - 1. 做的Android项目更多,Android开发也是经历了MVC==>MVP(依然是一种MVC架构)==>MVVM的模式演进。类MVC模式比较多,在UI相关的开发中,只用过贫血模式(之前也尝试过使用充血模式,但考虑到不一致带来的成本就放弃了);在UI无关的复杂服务类开发中,也用过充血模型(虽然我不知道它叫充血模型)。我认为贫血模型的优点是更容易看懂,充血模型的优点是更能应对复杂业务。
    - 2. 我认为还是不要放在同一个类中,原因是:成本大于收益。成本:一个复杂的类,在被不同的模块调用时充当着不同的角色,甚至,不同的模块调用不同的字段,需要大篇幅的文档来描述这些差异。稍有修改,复杂度的增加非线性。优点:代码重用。

    # 感想:
    软件开发处理的是工程学问题,解决方案依赖场景,一个新技术的火爆一定是解决了当前主流场景的痛点问题,随着规模和复杂度的变化,场景也随之变化;争论贫血模式更好还是充血模式更好,争论哪个开发语言更好,这样的问题都是伪命题,我们更应该投入精力的是为当前场景选择最合适的解决方案。
    2019-11-27
    15
  • ╭(╯ε╰)╮
    个人感觉业务被贫血模型绑架的另一个原因是以前缓存nosql这些技术不不成熟 刚毕业那会哪有什么redis,机器的内存也不多。都是公司堆在角落的旧机器。一些业务如果在domain里实现可能会hold住数据库中的大部分数据。所以业务上都需要翻译成sql的where和join来减少网络和内存的开销。功能都被sql抢了去,想充血也充不起来。现在随便开个项目不带个redis老板都会质疑一下。mysql的访问也都是能少就少,不行再多加几台云服务器。老板也显得更有面儿。
    2019-11-27
    4
    15
  • 墨雨
    老师,我平常做web开发都是,entity,dao,service,controller.对vo,bo不是很理解,也没有用到。有没有demo呢?

    作者回复: 我抽空写个demo,放到我的github上吧
    https://github.com/wangzheng0822

    2019-11-27
    8
    14
  • lizi
    沙发,不睡觉,听课。哈哈,加班好累,
    2019-11-27
    9
  • grey927
    能否用代码表达一下充血模型,其实还是不太理解

    作者回复: 下一节课有的

    2019-11-27
    6
  • 小晏子
    基本上经历过的web项目都是基于贫血模型开发模式的,entity,bo,vo不能放在一个类里,每个对象的应用场景不同,entity是映射数据库字段的,bo,vo适合业务和展示相关的,而且entity相对来讲变化不多,bo,vo可能会频繁变化,所以不适合放在同一个类里
    2019-11-27
    6
  • 花儿少年
    有本书叫ddd 原则,模式与实践可以翻阅一下
    首先要明白一点ddd适用范围,多数业务就是CRUD就可以搞定,理解起来也没有困难,为啥不继续用贫血模型。
    充血模型就像老师说的需要精心设计,以应对变化,如果没有一个复杂的业务场景就根本用不到,或者说用起来很难受是一个似是而非的东西。
    况且充血模型只建议用在核心域,还有通用域和支撑域呢,不要一上来就ddd
    2019-11-27
    5
  • 梦倚栏杆
    第一个还没有太多的感受,还需要时间来练习感受
    第二个是否合成一个各有优劣,可能还是和写代码人的功底有关:
    拆分开的优势:各层的防腐隔离,当前层的变化不影响其他层。
    拆分开的劣势:来一个迭代需求,比如需要加一下邮箱等, rd很有可能在三个类里各加一个字段,从上改到下,完全看不出隔离的优势,就看不到了一层层的类转换
    反过来就是不拆分的优劣
    2019-11-27
    5
  • Lrwin
    我觉得代码架构和业务架构一样,只要将关注点分离就可以。
    简单的系统,困难的不在于领域的拆分,而在于时间成本的控制。从软件工程角度考虑,时间,成本,范围三角理论可以进行分析、。
    我们所说的复杂系统,更看重业务的复杂度,将复杂度降低的方法则是分而治之。这样可以降低复杂度。
    复杂要解决三个问题:规模问题,结构问题和需求变化问题。无论是技术复杂度或业务复杂度,只要能解决这三种复杂度问题就是好的方法。
    DDD模型其实无异,都是将Model层做重。因为业务核心是技术无关的。传统MVC用于C/S模型,也依然是重Model层的。我觉得软件设计的方法没有变化,只是大家看到的视角不同罢了。
    软件架构有两个含义:1.参与的元素有哪些 2.元素间的关系是什么。 从抽象角度来看,非常简单。
    2019-11-27
    4
  • Geek_Zjy
    看到「领域驱动设计有点儿类似敏捷开发、SOA、PAAS 等概念,听起来很高大上,但实际上只值“五分钱”。」时,不知道引起了多少人的共鸣,O(∩_∩)O~。 做技术的本身就经常会遇到沟通问题,一些人还总喜欢“造概念”,唯恐别人听懂了,争哥这句话无疑说中了我们的心坎儿。
    当然我这里也不是说 DDD 不好(看后面的争哥也没这个意思),但是每个理论都有自己的局限性和适用性,看很多文章在讲一些理论时,总是恨不得把自己的理论(其实也算不得自己的)吹成银弹,态度上就让人很难接受。
    我还是喜欢争哥的风格,逻辑很清晰,也很严谨,很务实。

    关于老师的问题。
    说句实话,我们就没有写过充血模型的代码。
    我们会把 UserEntity、UserBo 混着用, UserBo 和 UserVo 之间转换时有时还会用 BeanUtils 之类的工具 copy 。
    对于复杂的逻辑,我们就用复杂 SQL 或者 Service 中的代码解决。

    不过我在翻一些框架时,比如 Java 的并发包时不可避免的需要梳理 Lock、Condition、Synchronizer 之间的关系。比如看 Spring IOC 时,也会需要梳理围绕着 Context 、 Factory 展开的很多类之间的关系。
    就好像你要“混某个圈子”时,就不可避免的“拜码头”,认识一堆“七大姑八大姨”,然后你才能理解整个“圈子”里的关系和运转逻辑。
    我也经常会有疑问, DDD 和面向对象究竟是什么关系,也会猜想:是不是面向对象主要关注“圈子”内的问题,而 DDD 主要关注“圈子”之间的问题?有没有高手可以回答一下。
    (其实我最近一直都想订隔壁DDD的课,但是考虑到精力的问题,以及担心学不会,主要不是争哥讲O(∩_∩)O~,所以没下手)

    作者回复: 哈哈,多谢认可,我写这篇文字的时候,还害怕搞DDD的人会来骂我,看来是我多虑了。隔壁的DDD课程可以去学下,管它是不是我写的,看看他咋“吹”的也好。

    2019-11-27
    2
    4
  • 丶Zero灬
    我们公司的ERP系统使用的是贫血模型,支付系统由一个OOP的忠实践行者设计的,使用的是充血模型。两个系统我都有在维护,先说ERP。遇到新需求的时候,就像老师说的SQL驱动。从后往前返回数据。简单的需求还好,像一些复杂的模块。看着service一个方法动辄几百,甚至出现过上千行的。真心感觉改不动。在业务还比较简单的时候贫血模型还够用,但随着业务发展,service层越来越重。这时还不做封装抽象,系统真心不好维护。很多时候都要依靠老员工的讲解。而,支付系统,在开发新功能的时候,因为封装抽象已经做的很好,改动起来还是蛮愉快的。没有那么多的重复性代码。不过,因为封装的太多太深,在刚接手的时候确实不好读。业务逻辑逻辑分散在各处。当然,也可能是本人水平有限。
    2019-11-28
    3
  • 编程界的小学生
    1.一直贫血模型,却没有发现。
    2.充血模型我有点没看懂,我粗糙的理解成把贫血模型的service里面的复杂业务逻辑都搬到了domain,然后service简单调用domain即可。那domain里不也是业务逻辑+bo.吗?还有复杂的sql无法复用问题,我一般写业务都是单表。然后放到service层调用多个dao去组装最终的数据出来。这样可以解决乱七八糟的sqlz吗?希望争哥解答下。
    3.第二道问题,我觉得不能合并,因为首先entity是对应的数据表字段,其次bo可能被多个业务方法所使用,返回的字段可能不同,这就需要vo来限制具体每个接口需要哪些字段来响应给客户端。
    2019-11-28
    3
  • 深度•仁
    一拍大腿,靠,说到心坎里去了,各种细碎的sql,就为了解决某个小功能! 业务熟悉,领域驱动设计就是屠龙刀,业务不熟悉,DDD也就值个半毛钱!茶不思,饭不想,期待后面的文章更新
    2019-11-28
    3
  • Geek_53aa5c
    Ruby on Rails的model层,ActiveRecord是很好的充血模型实现,借助Ruby语言动态的特性,CRUD以及属性映射不需要写一行代码一行配置文件就可以开箱即用,所以没有必要有repository这一层。
    2019-12-04
    2
  • 杨树敏
    回想起来经历的互联网项目一直都是贫血模型, 究其原因:
    1.互联网项目多采用敏捷开发, 需求多变, 设计充血模型的性价比低;
    2.互联网小产品业务场景相对简单, 业务难点更多的出现在并发, 性能上, 在存储,内存成本急剧下降的当下, 粗暴的横向扩展成为见效更快的选择.
    2019-11-28
    2
  • _呱太_
    小争哥,对设计模式这些概念都有了解,一直想找一些源码来看看顺便练下手,请问下有没有推荐的 C++ 比较经典的开源代码呢
    2019-11-28
    2
  • Lonely绿豆蛙
    最近边看边重构自己的项目,感觉真的是从码农视角转到了架构师层次~
    2019-11-28
    2
收起评论
99+
返回
顶部