写点什么

设计模式之美 - 面向对象对比面向过程有哪些优势?面向过程过时了吗?

作者:GalaxyCreater
  • 2022-11-12
    广东
  • 本文字数:3612 字

    阅读完需:约 12 分钟

OO 相对于面向过程有哪些优势?


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


对于简单程序的开发来说,不管是用面向过程编程风格,还是用面向对象编程风格,差别确实不会很大,甚至有的时候,面向过程的编程风格反倒更有优势。因为需求足够简单,整个程序的处理流程只有一条主线,很容易被划分成顺序执行的几个步骤,然后逐句翻译成代码,这就非常适合采用面向过程这种面条式的编程风格来实现。


但对于大规模复杂程序的开发来说,整个程序的处理流程错综复杂,并非只有一条主线。如果把整个程序的处理流程画出来的话,会是一个网状结构。如果我们再用面向过程编程这种流程化、线性的思维方式,去翻译这个网状结构,去思考如何把程序拆解为一组顺序执行的方法,就会比较吃力。这个时候,面向对象的编程风格的优势就比较明显了。


面向对象编程是以类为思考对象。在进行面向对象编程的时候,我们并不是一上来就去思考,如何将复杂的流程拆解为一个一个方法,而是采用曲线救国的策略,先去思考如何给业务建模,如何将需求翻译为类,如何给类之间建立交互关系,而完成这些工作完全不需要考虑错综复杂的处理流程。当我们有了类的设计之后,然后再像搭积木一样,按照处理流程,将类组装起来形成整个程序。这种开发模式、思考问题的方式,能让我们在应对复杂程序开发的时候,思路更加清晰。


OO 还提供了一种更清晰的,更模块化的代码组织方式。


2.OOP 风格的代码更易复用,易扩展,易维护

面向过程最主要的特点就是数据和方法相分离。相较于面向对象编程语言,面向过程编程语言最大的特点就是不支持丰富的面向对象编程特性,比如继承、多态、封装。

因为面向对象的语言具有封装,抽象(相对于面向过程功能更强的抽象),继承,多态特性。


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

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


Unix、Linux 这些复杂的系统,也都是基于 C 语言这种面向过程的编程语言开发的,你怎么看待这个现象?这跟我之前的讲解相矛盾吗?

操作系统更注重性能,OOP 更注重程序员的开发效率;随着硬件越来越便宜,OOP 越发流行,如果硬件还是和以前一样,没什么提供,安卓系统也不会用 java 开发,也不可能能建立那么大的生态。


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

1.滥用 getter、setter 方法

把每个属性的设置和获取都暴露出来


面向过程风格例子 1

// 一个购物车类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(); } // ...省略其他方法...}
复制代码
  • private 成员都定义了 get 和 set 方法,和 public 无异,破坏封装性。外部可以随意调用 set 方法,很容易导致属性不一致(如设置了 itemsCount,但没修改 items)。


例如:想实现一个清空购物车功能,如下实现:

ShoppingCart cart = new ShoppCart();
cart.getItems().clear(); // 清空购物车cart.setTotalPrice(0);cart.setItemCount(0);
复制代码

这种实现将 ShopingCart 的业务逻辑暴露给上层代码,应该定义一个 clear 方法,实现清空购物车功能。


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


2.滥用全局变量和全局方法


全局变量使用方式

将所有变量都放到一个叫 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:"; // ...省略更多的常量定义...}
复制代码


弊端:

  • 随着时间,这个类会越来越大,代码冲突概率越来越高

  • 还会增加编译时间,因为引用了这个类的文件太多,某个功能稍微修改一个字段,就会导致几乎全部模块重新编译。每次跑单元测试度会触发一次编译,影响开发效率。

  • 影响代码复用性。另外一个项目,复用本项目的类时,由于使用到了这个 Constants 类,只使用了小部分常量,引入了大量无关的常量。


被烂代码,烂设计折磨过的人才深有体会


改进

  1. 一般:尽量做到职责单一,每个模块一个 Constants 类。

  2. 更好:不单独地设计 Constants 常量类,而是哪个类用到了某个常量,我们就把这个常量定义到这个类中。比如,RedisConfig 类用到了 Redis 配置相关的常量,那我们就直接将这些常量定义在 RedisConfig 中,这样也提高了类设计的内聚性和代码的复用性。


Utils 类

只包含静态方法不包含任何属性的 Utils 类,是彻彻底底的面向过程的编程风格。但这并不是说,我们就要杜绝使用 Utils 类了。实际上,从刚刚讲的 Utils 类存在的目的来看,它在软件开发中还是挺有用的,能解决代码复用问题。所以,这里并不是说完全不能用 Utils 类,而是说,要尽量避免滥用,不要不加思考地随意去定义 Utils 类。


3.定义数据和方法分离的类

即:数据定义在一个类中,方法定义在另一个类中。


后端开发的传统的 MVC 结构分为 Model 层、Controller 层、View 层这三层。不过,在做前后端分离之后,三层结构在后端开发中,会稍微有些调整,被分为 Controller 层、Service 层、Repository 层。Controller 层负责暴露接口给前端调用,Service 层负责核心业务逻辑,Repository 层负责数据读写。而在每一层中,我们又会定义相应的 VO(View Object)、BO(Business Object)、Entity。一般情况下,VO、BO、Entity 中只会定义数据,不会定义方法,所有操作这些数据的业务逻辑都定义在对应的 Controller 类、Service 类、Repository 类中。这就是典型的面向过程的编程风格。这种开发模式叫做基于贫血模型的开发模式。


为什么那么容易写出面向过程风格的代码?

在生活中,你去完成一个任务,你一般都会思考,应该先做什么、后做什么,如何一步一步地顺序执行一系列操作,最后完成整个任务。面向过程编程风格恰恰符合人的这种流程化思维方式。而面向对象编程风格正好相反。它是一种自底向上的思考方式。它不是先去按照执行流程来分解任务,而是将任务翻译成一个一个的小的模块(也就是类),设计类之间的交互,最后按照流程将类组装起来,完成整个任务。这样的思考路径比较适合复杂程序的开发,但并不是特别符合人类的思考习惯。

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


面向过程编程风格无用武之地吗?

如果我们开发的是微小程序,或者是一个数据处理相关的代码,以算法为主,数据为辅,那脚本式的面向过程的编程风格就更适合一些。当然,面向过程编程的用武之地还不止这些。实际上,面向过程编程是面向对象编程的基础,面向对象编程离不开基础的面向过程编程。为什么这么说?我们仔细想想,类中每个方法的实现逻辑,不就是面向过程风格的代码吗?


不管使用面向过程还是面向对象哪种风格来写代码,我们最终的目的还是写出易维护、易读、易复用、易扩展的高质量代码。只要我们能避免面向过程编程风格的一些弊端,控制好它的副作用,在掌控范围内为我们所用,我们就大可不用避讳在面向对象编程中写面向过程风格的代码。


用户头像

GalaxyCreater

关注

还未添加个人签名 2019-04-21 加入

还未添加个人简介

评论

发布
暂无评论
设计模式之美-面向对象对比面向过程有哪些优势?面向过程过时了吗?_设计模式_GalaxyCreater_InfoQ写作社区