面向对象与面向过程

面向过程的定义

面向过程编程 是一种编程范式或编程风格。

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

与面向对象的区别

代码的组织方式不同

OOP 更加能够应对大规模复杂程序的开发

OOP 风格的代码更易复用、易扩展、易维护

面向过程编程是一种非常简单的编程风格,并没有像面向对象编程那样提供丰富的特性。

OOP 语言更加人性化、更加高级、更加智能

跟二进制指令、汇编语言、面向过程编程语言相比,面向对象编程语言的编程套路、思考问题的方式,是完全不一样的。前三者是一种计算机思维方式,而面向对象是一种人类的思维方式。
进行面向对象编程时候,我们是在思考,如何给业务建模,如何将真实的世界映射为类或者对象,这让我们更加能聚焦到业务本身,而不是思考如何跟机器打交道。
可以这么说,越高级的编程语言离机器越“远”,离我们人类越“近”,越“智能”。

假面向对象

滥用 getter、setter 方法

为每个属性都定义 gettersetter 方法,违反了面向对象编程的封装特性,相当于将面向对象编程风格退化成了面向过程编程风格。如:


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();
  }

  // ...省略其他方法...
}
  • itemsCounttotalPrice。虽然我们将它们定义成 private 私有属性,但是提供了 publicgettersetter 方法,这就跟将这两个属性定义为 public 公有属性,没有什么两样了。
  • 任何代码都可以随意调用 setter 方法,来重新设置 itemsCounttotalPrice 属性的值,这也会导致其跟 items 属性的值不一致。
  • 暴露不应该暴露的 setter 方法,明显违反了面向对象的封装特性。
  • items 属性返回的是一个 List 集合容器。外部调用者在拿到这个容器之后,是可以操作容器内部数据的,也就是说,外部代码还是能修改 items 中的数据,导致 itemsCounttotalPriceitems 三者数据不一致。

在设计实现类的时候,除非真的需要,否则,尽量不要给属性定义 setter 方法。除此之外,尽管 getter 方法相对 setter 方法要安全些,但是如果返回的是集合容器(比如例子中的 List 容器),也要防范集合内部数据被修改的危险。

滥用全局变量和全局方法

在面向对象编程中,常见的全局变量有:单例类对象静态成员变量常量等,常见的全局方法有静态方法。静态方法将方法与数据分离,破坏了封装特性,是典型的面向过程风格。

Constants 类

一些代码中的配置参数,一般都设置为常量,放到一个配置类中。如:


public class Constants {
  public static final String MYSQL_ADDR_KEY = "mysql_addr";
  public static final String MYSQL_DB_NAME_KEY = "db_name";
  public static final String MYSQL_USERNAME_KEY = "mysql_username";
  public static final String MYSQL_PASSWORD_KEY = "mysql_password";
  
  public static final String REDIS_DEFAULT_ADDR = "192.168.7.2:7234";
  public static final int REDIS_DEFAULT_MAX_TOTAL = 50;
  public static final int REDIS_DEFAULT_MAX_IDLE = 50;
  public static final int REDIS_DEFAULT_MIN_IDLE = 20;
  public static final String REDIS_DEFAULT_KEY_PREFIX = "rt:";
  
  // ...省略更多的常量定义...
}

弊端:

改进:

Utils 类

将没有继承关系的两个类中,具有相同功能逻辑的部分抽取出来,定义为只包含静态方法的 Utils 类。达到代码复用的目的。

只包含静态方法不包含任何属性的 Utils 类,是彻彻底底的面向过程的编程风格。但这并不是说,我们就要杜绝使用 Utils 类了。
而是要尽量避免滥用,不要不加思考地随意去定义 Utils 类。

设计 Utils 类的时候,最好也能细化一下,针对不同的功能,设计不同的 Utils 类,比如 FileUtilsIOUtilsStringUtilsUrlUtils 等,不要设计一个过于大而全的 Utils 类。

定义数据和方法分离的类

基于贫血模型的开发模式:

基于 MVC 三层结构做 Web 方面的后端开发,经常会将数据定义在一个类中,方法定义在另一个类中。
我们会定义相应的 VO(View Object)声明接口返回的数据、BO(Business Object)、Entity。一般情况下,VOBOEntity 中只会定义数据,不会定义方法,所有操作这些数据的业务逻辑都定义在对应的 Controller 类、Service 类、Repository 类中。这就是典型的面向过程的编程风格。

详见 充血模型和贫血模型

易于面向过程的原因

思维方式不同

复杂度不同

在面向对象编程中,类的设计还是挺需要技巧,挺需要一定设计经验的。你要去思考如何封装合适的数据和方法到一个类里,如何设计类之间的关系,如何设计类之间的交互等等诸多设计问题。