IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 重新认识面向对象——Java写了五年,你真的弄明白什么是面向对象了吗?不,你一直都是在面向过程编程 -> 正文阅读

[Java知识库]重新认识面向对象——Java写了五年,你真的弄明白什么是面向对象了吗?不,你一直都是在面向过程编程

什么是面向过程编程

“面向过程”(Procedure Oriented,简称PO)是一种以过程为中心的编程思想。其原理就是将问题分解成一个一个详细的步骤,然后通过函数实现每一个步骤,并依次调用。

面向过程,其实核心就是“过程”两个字。过程指的是解决问题的步骤,即先干什么、后干什么、再干什么、然后干什么……

面向过程编程是一种编程范式或编程风格。它以过程(可以理解为方法、函数、操作)作为组织代码的基本单元,以数据(可以理解为成员变量、属性)与方法相分离为最主要的特点。面向过程风格是一种流程化的编程风格,通过拼接一组顺序执行的方法来操作数据完成一项功能。

面向过程编程语言首先是一种编程语言。它最大的特点是不支持类和对象两个语法概念,不支持丰富的面向对象编程特性(比如继承、多态、封装),仅支持面向过程编程。

什么是面向对象编程

面向对象编程中有两个非常重要、非常基础的概念,那就是类(class)和对象(object)。

面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。

面向对象编程语言是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。

面向对象编程相比较面向过程而言,多了很多巨大的优势:更加能够应对大规模复杂程序的开发;风格的代码更易复用、易扩展、易维护;语言更加人性化、更加高级、更加智能。

  • 对于大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。
  • 面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。
  • 从编程语言跟机器打交道的方式的演进规律中,我们可以总结出:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。

用着面向对象语言,却一直干着面向过程的事

springboot - web开发,也许你一直都在面向过程开发

日常开发过程中,很多业务系统都是基于 MVC 三层架构来开发的,实际上,大部分朋友开发的 过程中,一直都是基于“贫血模型” 面向过程开发的,虽然定义了很多model、vo、po,但是不可否认,这的确是一种面向过程开发。

虽然这种开发模式已经成为标准的 Web 项目的开发模式,但它却违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格,因此而被有些人称为反模式(anti-pattern)。特别是领域驱动设计(Domain Driven Design,简称 DDD)盛行之后,这种基于贫血模型的传统的开发模式就更加被人诟病。而基于充血模型的 DDD 开发模式越来越被人提倡。

滥用getter、setter

在之前参与的项目开发中,我经常看到,有同事定义完类的属性之后,就顺手把这些属性的 getter、setter 方法都定义上。有些同事更加省事,直接用 IDE 或者 Lombok 插件(如果是 Java 项目的话)自动生成所有属性的 getter、setter 方法。

当我问起,为什么要给每个属性都定义 getter、setter 方法的时候,他们的理由一般是,为了以后可能会用到,现在事先定义好,类用起来就更加方便,而且即便用不到这些 getter、setter 方法,定义上它们也无伤大雅。

实际上,这样的做法我是非常不推荐的。它违反了面向对象编程的封装特性,相当于将面向对象编程风格退化成了面向过程编程风格。

提供了 public 的 getter、setter 方法,这就跟将这两个属性定义为 public 公有属性,没有什么两样了。外部可以通过 setter 方法随意地修改这两个属性的值。除此之外,任何代码都可以随意调用 setter 方法。这完全违反了面向对象的封装、访问权限控制。

例如我们定义购物车,item表示购物车里面的商品,totalPrice记录总价格


public class ShoppingCart {
  private int itemsCount;
  private double totalPrice;
  private List<ShoppingCartItem> items = new ArrayList<>();
  
  public int getItemsCount() {
    return this.itemsCount;
  }
  
  public void setItemsCount(int itemsCount) {
    this.itemsCount = itemsCount;
  }
  
  public double getTotalPrice() {
    return this.totalPrice;
  }
  
  public void setTotalPrice(double totalPrice) {
    this.totalPrice = totalPrice;
  }

  public List<ShoppingCartItem> getItems() {
    return this.items;
  }

  // ...省略其他方法...
}

里面的所有参数都写了get、set方法,这意味着我们可以在外部任意操作每一个属性。
比如说我们添加一个商品,或者清空购物车,可能在service中是这样实现的:

ShoppingCart cart = new ShoppCart();
...

cart.getItem().add(item); // 添加商品
cart.setItemsCount(...); // itemsCount++;
cart.setTotalPrice(...); // 计算总金额
...
cart.getItems().clear(); // 清空购物车、计算总金额、计算总商品数
...

你可能会说,清空购物车这样的功能需求看起来合情合理啊,上面的代码没有什么不妥啊。你说得没错,需求是合理的,但是这样的代码写法,是典型的面向过程的编程,假如说清除购物车的代码,少写了一句计算总金额的逻辑,那就会直接导致购物车商品不正确;而且通过get方式获取的商品list,可以随意的往里面添加值删除值,这完全违背了面向对象的封装特性。

而正确做法是,将这部分逻辑进行封装,取消不必要的set方法:


public class ShoppingCart {
  private int itemsCount;
  private double totalPrice;
  private List<ShoppingCartItem> items = new ArrayList<>();
  
  public int getItemsCount() {
    return this.itemsCount;
  }

  public double getTotalPrice() {
    return this.totalPrice;
  }

  public List<ShoppingCartItem> getItems() {
    // 使用原型模式返回一个与原对象无关的对象,防止随意篡改
  }
  
  public void addItem(ShoppingCartItem item) {
    items.add(item);
    itemsCount++;
    totalPrice += item.getPrice();
  }
  // ...省略其他方法...
}

基于“贫血模型”传统开发模式

MVC 三层架构中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:展示层、逻辑层、数据层。

通常我们Model层只定义了接口的入口,使用@Controller接收参数;数据层一般连接数据库,写一些sql语句;service层才是真正的核心逻辑层。

就像是上面滥用getter、setter的例子,相信各位小伙伴的项目代码中不占少数。

这是一种典型的面向过程开发的模式,也就是“贫血模型”。

像 UserBo 这样,只包含数据,不包含业务逻辑的类,就叫作贫血模型(Anemic Domain Model)。


// Controller+VO(View Object) //
public class UserController {
  private UserService userService; //通过构造函数或者IOC框架注入
  
  public UserVo getUserById(Long userId) {
    UserBo userBo = userService.getUserById(userId);
    UserVo userVo = [...convert userBo to userVo...];
    return userVo;
  }
}

public class UserVo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

// Service+BO(Business Object) //
public class UserService {
  private UserRepository userRepository; //通过构造函数或者IOC框架注入
  
  public UserBo getUserById(Long userId) {
    UserEntity userEntity = userRepository.getUserById(userId);
    UserBo userBo = [...convert userEntity to userBo...];
    return userBo;
  }
}

public class UserBo {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

// Repository+Entity //
public class UserRepository {
  public UserEntity getUserById(Long userId) { //... }
}

public class UserEntity {//省略其他属性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

基于“充血模型”的 DDD 开发模式

领域驱动设计,即 DDD,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。领域驱动设计这个概念并不新颖,早在 2004 年就被提出了,到现在已经有十几年的历史了。不过,它被大众熟知,还是基于另一个概念的兴起,那就是微服务。

在贫血模型中,数据和业务逻辑被分割到不同的类中。充血模型(Rich Domain Model)正好相反,数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。

在基于贫血模型的传统开发模式中,Service 层包含 Service 类和 BO 类两部分,BO 是贫血模型,只包含数据,不包含具体的业务逻辑。业务逻辑集中在 Service 类中。在基于充血模型的 DDD 开发模式中,Service 层包含 Service 类和 Domain 类两部分。Domain 就相当于贫血模型中的 BO。不过,Domain 与 BO 的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑。而 Service 类变得非常单薄。总结一下的话就是,基于贫血模型的传统的开发模式,重 Service 轻 BO;基于充血模型的 DDD 开发模式,轻 Service 重 Domain。

为什么“贫血模型”的面向过程盛行

环境问题:

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

个人问题:

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

努力向“充血模型”靠拢

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

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

吃透面向对象的基本思想,充分理解面向对象的设计原则与设计模式,让你的开发事半功倍。

参考资料

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 11:40:47  更:2022-10-31 11:46:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 4:26:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码