设计原则与思想: 面向对象(理论四)

85 阅读4分钟

引言:

哪些代码设计看似是面向对象, 实际是面向过程的?

只运用面向对象语言是不够的, 更重要的是运用面向对象的思想. 否则写出来的代码还是面向过程编程风格的.

一. 哪些代码看似是面向对象, 实际是面向过程的

下面举3个最典型的例子来学习

1.1 滥用getter, setter方法

现如今, 公司开发中有很多不规范, 如:Lombok插件, 它违背了面向对象的封装特性, 相当于将面向对象编程风格退化成了面向过程风格.


//购物车类
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;
  }
  
  public void addItem(ShoppingCartItem item) {
    items.add(item);
    itemsCount++;
    totalPrice += item.getPrice();
  }
  // ...省略其他方法...
}

首先, 2个属性: itemsCount和totalPrice. 虽然它们都定义了private, 但是提供了public的getter和setter方法, 这跟直接订单2个属性为public没什么区别.外部都可以随意通过setter方法修改属性的值.

其次, 再看一下items, 虽然没有提供setter方法, 但是提供了getter方法, 外部获取这个list集合后, 可以对集合中的数据进行修改, 如: cart.getItems().clear();, 直接就会清空购物车.

针对第二点, 有其他的解决办法吗? Collections.unmodifiableList(), 我们可以返回一个不可变的集合给外部, 当外部调用add, clear方法时, 就会抛异常.

即便这样, 调用者也可以通过getItems获取items后, 遍历修改每个对象中的属性.

那么有啥更好的解决方法不? 可不可以clone一个新的集合返回给调用者呢?

1.2 滥用全局变量和全局方法

滥用常量类的弊端:

  1. 常量多会变的不好维护(一般都是拆分出多个, 单一职责, 如: RedisConstants, MysqlConstants)
  2. 编译耗时(由于一个常量依赖的代码较多, 导致每次修改这个常量时, 都会导致依赖它的类文件重新编译, 浪费了不必要的时间)
  3. 代码不复用(有时候为了一个常量, 引入了一个巨大的常量类)

1.2.1 Utils类存在的意义

问题背景:

如果我们有2个类, 分别是:A和B, 他们用到了一块相同的逻辑, 为了避免重复, 我们应该怎么办?

我们一般会想到面向对象特性: 继承, 但是如果A和B不一定是继承关系, 也不是兄弟关系, 这时候你抽象出一个无关的父类, 肯定会影响代码的可读性.

所以这时候我们可以定义一个Utils类, 用来处理那一块公共的代码.

1.3 定义数据和方法分离的类

数据和方法逻辑分离, 这是典型的面向过程编程模式

传统的MVC三层架构, controller:负责暴露接口给前端, service:负责核心业务逻辑, repository:负责数据读写. 这其实就是典型的面向过程编程风格

这种开发模式叫作: 贫血模型的开发模式. 后面会慢慢讲解.

二. 在面向对象编程中, 为什么容易写出面向过程风格的代码?

因为面向过程编程, 是一种流程化的, 更符合人类的思维. 我们一般思考问题都是: 第一步做什么, 第二步做什么, 最后再做什么.

而面向对象编程, 它是一种自底向上的思考方式, 不按照流程去拆解, 而是按照流程将类组装起来, 完成任务.

三. 面向过程编程及面向过程编程语言真的就无用武之地了吗?

面向过程不一定坏, 面向对象不一定好. 只是面向对象更加能解决复杂的逻辑.

我们最终的目的, 就是要写出易维护, 易读, 易扩展的代码.

四. 课堂讨论

4.1 题1

问: 看似面向对象实际面向过程的编程风格的代码有很多, 除了今天讲的三个, 你还遇到过哪些?

答: 没有抽象和继承, 一个功能复制多份