浅谈面向对象编程与面向过程编程以及DDD充血Model

浅谈面向对象编程与面向过程编程以及DDD充血Model

先抛出开头一个问题:一直在说OOP面向对象编程,你现在写的代码真的是OOP吗?你确定你写的不是PO(面向过程)的代码?

面向对象编程和面向过程编程

解答上面的问题,首先来回顾一下什么是面向对象编程和面向过程编程,回顾前先说一下一个误区。

  • 误区: 面向对象编程语言 不等于 面向对象编程

    可能会有人说我用的Java怎么就被说成写面向过程?

    1. Java是一种面向对象编程语言,面向对象编程语言只是提供易于面向对象编程的语法,例如:extends实现继承、interface、abstract class实现多态等,让你更容易地面向对象编程
    2. 面向对象编程是一种编程开发制品的思想,面向对象编程的四项基本原则:继承、抽象、封装、多态,也就是说如果你的代码符合这个原则范式,那么你就是面向对象编程。举个例子,Linux内核是用c编写的,但是很多地方都会有面向对象的身影,例如:VFS文件系统,调度系统等等。再多说一下,思想是通用的,是思考的方式(模式),思想来源于现实也反映现实,计算机体系很多思想也是来源于现实,例如遗传算法、神经网络等等
    3. 那么能够用Java一面向对象编程语言写面向过程的代码是不是就很合理呢?
  • 面向对象编程和面向过程编程的区别

    • 面向对象是以类为组织代码的基本单元,面向过程是以过程(或方法)组织代码的基本单元
    • 主要特点:数据和方法分离
    • 而面向对象也具有上面说的四种基本原则:继承、抽象、封装、多态

违背OOP的贫血模型(反模式anti-pattern)

你写的Service层十有八九是POP!

回忆一下MVC架构,MVC分别就是数据层Model、展示层View、控制层Contoller。

而自从前后端分离之后,后端又可以分成Repostory数据层、Service业务逻辑层、Controller控制器层。

  • Repository层主要负责各个DB的读取以及对象转换(在相对简单项目各种ORM或许就能完成需求,可是在复杂场景ORM自身或许有点力不从心,例如复杂ERP、电商等等,就会是各种的SQL乱飞)
  • Service层负责主要业务逻辑
  • Controller层负责暴露Restful接口(有可能有的非Restful)。

那么,一个项目主要逻辑主要在Service层,而目前项目的Service几乎都是POP!为什么这样说呢?

先看看一段基于MVC后端代码(例子可能会有不规范地方):

public class UserController {
  private UserService userService;
  public UserVo getUserById(...){
    UserBo userBo = userService.getUserById(..);
    UserVo userVo = convertToVo(userBo);
    return userVo;
  }
}
@Data//lombok注解
public class UserVo{
  private String userId;
  private String userName;
  ...
}
public class UserService{
  public UserBo getUserById(...){
    UserDo userDo = userRepository.getUserById();
    UserBo userBo = convertToBo(userBo);
    return userBo;
  }
}
@Data//lombok注解
public class UserBo{
  private String userId;
  private String userName;
  ...
}
public class UserRepository {
  public UserDo getUserById(..) {
    .....read through db/cache
      return userDo;
  }
}
@Data//lombok注解
public class UserDo{
  private String userId;
  private String userName;
  ...
}

这应该目前最常用的代码架构,即使可能会有些公司有适当改造,但是大同小异

  • Controller+VO
  • Service+BO(有的甚至直接使用DO/Entity)
  • Repository+DO

贫血模型

先抛开Repository层和Controller层,看我们先把关注点放到代码量最多的Service层的BO

不知道到这里对比上面说过的大家有没有发现以下几点:

  • BO并不包含任何的业务逻辑,只是包含数据,这不是正正是面向过程编程的特征?
  • 而像Bo这种不包含任何业务逻辑的类,通常就被称为贫血模型
  • 而贫血模型将数据和操作分离,破坏了面向对象的封装特性,就是一种典型的面向过程的风格。

Bo滥用lombok真的好吗?

可能会有同学注意到我们特意标明了一个lombok注解。然后回问一句Bo这个类不是使用了getter和setter从而具有封装的思想吗?那么使用lombok为所有的成员变量设置getter和setter真的好吗?

一般使用lombok设置所有的setter和getter的理由是:以后可能会用到啊,现在顺手定义无伤大雅,而且以后使用更方便。

再来看一个购物车例子:

public class ShoppingCart{
  @Setter
    @Getter
  private int itemCount;
  @Setter
    @Getter
  private int totalPrice;
    @Getter
  private List<ShoppingCartItem> items = new ArrayList<>();
  public void addItem(ShoppingCartItem item) {
    items.add(item);
    itemsCount++;
    totalPrice += item.getPrice();
  }
}
  • 这是一个购物车类,有三个属性,itemCount,totalPrice,items三个属性

    • itemCount,totalPrice分别设置了setter和getter,那么跟public有什么区别?是否有些自欺欺人?

    • 而items没有定义setter,而是封装了addItem。可是这样真的没问题吗?来看一段访问代码

      public class CartServie{
          public void addItem(ShoppingCartItem item) {
          ShoppingCart shoppingCart = 通过id拿出shoppingCart;
          shoppingCart.getItems.add(item);
        }
      }
      
      • 如果多个同学共同开发,没有留意到addItem,很可能写出这样的代码,直接通过getItems拿到内存中的items的对象,往里面塞item对象,整个shoppingCart类数据是否不一致了?
  • 面向对象封装的定义:通过权限访问控制,隐藏内部数据,外部仅通过类提供的有限接口访问、修改内部数据。不应该暴露不该又的setter方法,甚至一些容器的getter方法。

  • 如果我又想使用list的getter方法怎么办呢?其实java提供了Collections.unmodifiedList()方法,请同学自行查阅。

充血模型

既然有贫血模型,那么肯定会有充血模型,bingo,充血模型也是存在的。

  • 充血模型:数据和业务逻辑封装到同一个类,而且是符合面向对象编程的。

这里不得不说一下DDD领域驱动设计,DDD的核心概念其实就是利用充血模型开发,DDD其实早诞生于2004年,只不过蹭了一波微服务热度。

众所周知,微服务总是离不开调用链、监控、API网关等服务治理的工具(微服务的交错复杂特点带来的,这里顺带提一下混沌工程,用于检测分布式系统潜在漏洞的科学),微服务的另一个难点就是服务拆分,而DDD刚好可以填上这一块难点。

实际上,基于充血模型DDD开发的代码,还是基于MVC架构,与传统模式贫血模型开发的区别在于service层

  • 充血模型,service层包含domain类和service类,但是区别在于domain类既包含数据也包含业务逻辑,service的功能相对被削弱。

    换句话说,充血模型DDD开发模式重domain轻service,而贫血模型重service轻BO

为什么贫血模型那么流行?

  • 不知道有没有人发现Spring官方的demo也是使用贫血模型?

  • 那么贫血模型模型开发为什么那么流行?

    1. SQL-Driven开发

      目前大部分开发都是基于数据库,也就是常说的SQL-Driven开发。开发前,只需要定义好相对应的表,然后拿来需求往service类上堆代码就可以。充血模型开发前需要精心设计,而且如果需求简单,其实贫血模型和充血模型并无两样。贫血模型开发速度更快,其实这当中个人觉得会有一定敏捷开发的原因,现代互联网公司迭代快,有的甚至太过于追求开发交付速度。

    2. 充血模型开发有难度

      目前国内大部分开发者其实更符合码农的角色,java developer来说基于spring打天下,基于贫血模型,定义好数据,直接读取在service类处理一下就可以返回。然而充血模型在设计期需要花费不少精力设计,明确每个类的“职责”,需要暴露哪些业务逻辑(符合GRASP),开发成本高。

    3. 思维固化

      世界上最难改变的两者之一就是思想,人不是纯理性的动物,否则就变成冷冰冰的机器。大部分的developer已经习惯了贫血模型的开发,潜意识也是很恐怖的,实用派就会觉得我基于贫血模式开发的业务系统也没出过问题,何必要增加改变成本?当业务上没有遇到痛点,是很难发生改变的。

什么项目适合充血模型DDD?

最后,重申一点,DDD不是银弹,不要盲目追求DDD,简单的业务系统或者特定的业务场景有时更适合贫血模型开发。

这让我回想起当初滥用parallelStream差点把应用拖垮,《Java并发编程艺术》第一章也不是教大家如何去更好使用并发容器或者工具,而是说明一个情况,不是所有场景都适用多线程,上下文切换也有很大消耗,正如AIO NIO不一定优于BIO,Redis除了收发请求Reactor和持久化的fsync线程,执行引擎使用的是单线程。

那么什么项目适合充血模型DDD?

  • 复杂业务系统,例如包含各种利息计算模型、还款模型的金融业务系统
  • 当你发现你的Service已经臃肿不堪,不能再往上堆代码需要重构的时候可以考虑
  • 当使用DDD重构时候,请务必熟悉业务架构,业务驱动开发,即使你对DDD概念再深入,不熟悉业务架构是设计不出一流的DDD架构的

最后

充血模型不能只是代码层面去思考,更需要思维转变

  • 贫血模型,上面说到的SQL-Driven,接到需求,看接口需要的数据对应在哪张表,利用SQL语句读出来然后做一些业务逻辑,然后模版式地往Repository、Service和Controller类添加代码。过程中,很少人使用OOP,领域模型的概念,甚至有的连代码复用也没有
  • 充血模型,先理清楚有哪些业务,业务领域模型的职责(成员变量和方法),领域模型其实更类似可复用的业务中间层。新功能开发可以基于各个领域对象完成。而不再是基于BO往上堆代码(基于数据库开发,很容易造成service臃肿)。

本文章是学习OOP的总结,有不当之处请指出批评,本文参考以下并不限于:

王争老师《设计模式之美》

Craig Larman《UML和模式应用》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343