设计模式学习(8)

105 阅读11分钟

面向对象与面向过程

很多人搞不清楚面向对象和面向过程的区别,总以为使用面向对象编程语言来做开发,就是在进行面向对象编程了。而实际上,只是在用面向对象编程语言,编写面向过程风格的代码而已,并没有发挥面向对象编程的优势。

  • 什么是面向过程编程、语言?
  • 面向对象编程相比面向过程编程有哪些优势?
  • 为什么说面向对象编程语言比面向过程编程语言更高级?

定义:

  • 面向过程编程是一种编程范式、风格。它以过程(可以为理解方法、函数、操作)作为组织代码的基本单元,以数据(可以理解为成员变量、属性)与方法相分离为最主要的特点。面向过程风格是一种流程化的编程风格,通过拼接一组顺序执行的方法来操作数据完成一项功能。
  • 面向过程编程语言首先是一种编程语言。它最大的特点是不支持类和对象两个语法概念,不支持丰富的面向对象编程特性(比如继承、多态、封装),仅支持面向过程编程。

代码案例:假设有一个记录了用户信息的文本文件 users.txt,每行文本的格式是 name&age&gender(比如,小王 &28& 男)。希望写一个程序,从 users.txt 文件中逐行读取用户信息,然后格式化成 name\tage\tgender(其中,\t 是分隔符)这种文本格式,并且按照 age 从小到达排序之后,重新写入到另一个文本文件 formatted_users.txt 中。

以面向过程编程的代码(c)实现:

 struct User {
     char name[64];
     int age;
     char gender[16];
 };
 ​
 struct User parse_to_user(char* text) {
     // 将 text(“小王 &28& 男”) 解析成结构体 struct User
 }
 ​
 char* format_to_text(struct User user) {
     // 将结构体 struct User 格式化成文本(" 小王\t28\t 男 ")
 }
 ​
 void sort_users_by_age(struct User users[]) {
     // 按照年龄从小到大排序 users
 }
 ​
 void format_user_file(char* origin_file_path, char* new_file_path) {
     // open files...
     struct User users[1024]; // 假设最大 1024 个用户
     int count = 0;
     while(1) { // read until the file is empty
         struct User user = parse_to_user(line);
         users[count++] = user;
     }
 ​
     sort_users_by_age(users);
     
     for (int i = 0; i < count; ++i) {
         char* formatted_user_text = format_to_text(users[i]);
         // write to new file...
     }
     // close files...
 }
 ​
 ​
 int main(char** args, int argv) {
     format_user_file("/home/user.txt", "/home/formatted_users.txt");
 }

这段C语言代码是一个简单的示例,展示了如何解析文本数据、将数据存储在结构体中、排序结构体数组并将结果格式化输出到文件中。

代码中定义了一个struct User结构体,包含了用户的姓名、年龄和性别。然后,定义了几个函数来处理用户数据:

  1. struct User parse_to_user(char* text):这个函数接受一个文本字符串作为参数,将其解析为一个struct User结构体,并返回该结构体。该函数的具体实现可以将字符串按照一定的规则进行解析,提取出姓名、年龄和性别,并将其存储在一个struct User对象中后返回。
  2. char* format_to_text(struct User user):这个函数接受一个struct User结构体作为参数,将其格式化为一个文本字符串,并返回该字符串。函数的实现可以将struct User中的各个字段按照一定的格式拼接成一个字符串,例如使用制表符分隔字段。
  3. void sort_users_by_age(struct User users[]):该函数接受一个struct User结构体数组作为参数,按照用户的年龄从小到大对数组进行排序。函数的实现可以使用任何适合的排序算法,例如冒泡排序或快速排序。
  4. void format_user_file(char* origin_file_path, char* new_file_path):这个函数用于处理用户数据文件。它打开原始文件和新文件,然后读取原始文件中的每一行数据,将其解析为struct User结构体,并存储在一个struct User结构体数组中。然后,它调用sort_users_by_age函数对数组进行排序,再遍历排序后的数组,使用format_to_text函数将每个用户的数据格式化为文本,并将结果写入新文件中。

main函数中,调用format_user_file函数来处理用户数据文件。它将原始文件路径和新文件路径作为参数传递给format_user_file函数,以指定要处理的文件。

扩展:

在C语言中,结构体(Struct)是一种自定义的数据类型,用于组合多个不同类型的变量(成员)以形成一个更大的数据结构。

结构体允许你在一起存储多个相关的数据项,并为这些数据项定义一个新的类型。它可以包含不同类型的成员变量,如整数、字符、浮点数、指针等。

结构体的定义使用struct关键字,后跟结构体的名称和一对花括号{},花括号中包含结构体的成员变量的声明。每个成员变量的声明包括其数据类型和变量名。

以下是一个示例,展示了如何定义和使用一个简单的结构体:

 struct Person {  // 定义了一个名为Person的结构体
   char name[50]; // 三个成员变量:name(字符数组)、age(整数)和height(浮点数)
   int age;
   float height;
 };
 ​
 // 主函数
 int main() {
   struct Person person1; // 声明一个名为person1的Person结构体变量
   strcpy(person1.name, "John"); // 设置name成员的值为"John" strcpy是C语言中的一个字符串操作函数,用于将一个字符串复制到另一个字符串中。
   person1.age = 25; // 设置age成员的值为25
   person1.height = 1.75; // 设置height成员的值为1.75
 ​
   printf("Name: %s\n", person1.name);
   printf("Age: %d\n", person1.age);
   printf("Height: %.2f\n", person1.height);
 ​
   return 0;
 }

结构体可以用于创建更复杂的数据结构,表示实体对象、记录、配置项等。通过结构体,你可以将相关的数据组织在一起,提高代码的可读性和可维护性。

用面向对象编程的代码(java)实现

 public class User {
     private String name;
     private int age;
     private String gender;
     
     public User(String name, int age, String gender) {
         this.name = name;
         this.age = age;
         this.gender = gender;
     }
     
     public static User praseFrom(String userInfoText) {
         // 将 text(“小王&28&男”) 解析成类 User
     }
 ​
     public String formatToText() {
         // 将类 User 格式化成文本(" 小王\t28\t 男 ")
     }
 }
 ​
 public class UserFileFormatter {
     public void format(String userFile, String formattedUserFile) {
         // Open files...
         List users = new ArrayList<>();
         while (1) { // read until file is empty
             // read from file into userText...
             User user = User.parseFrom(userText);
             users.add(user);
         }
         // sort users by age...
         for (int i = 0; i < users.size(); ++i) {
             String formattedUserText = user.formatToText();
             // write to new file...
         }
         // close files...
     }
 }
 ​
 public class MainApplication {
     public static void main(Sring[] args) {
         UserFileFormatter userFileFormatter = new UserFileFormatter();
         userFileFormatter.format("/home/zheng/users.txt", "/home/formatted_users.txt")
     }
 }

从上面的代码中可以看出,面向过程和面向对象最基本的区别就是,代码的组织方式不同。面向过程风格的代码被组织成了一组方法集合及其数据结构(struct User),方法和数据结构的定义是分开的。面向对象风格的代码被组织成一组类,方法和数据结构被绑定一起,定义在类中。

面向对象编程的优势

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

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

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

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

面向对象编程还提供了一种更加清晰的、更加模块化的代码组织方式。比如,开发一个电商交易系统,业务逻辑复杂,代码量很大,可能要定义数百个函数、数百个数据结构,那如何分门别类地组织这些函数和数据结构,才能不至于看起来比较凌乱呢?类就是一种非常好的组织这些函数和数据结构的方式,是一种将代码模块化的有效手段。

像 C 语言这种面向过程的编程语言,也可以按照功能的不同,把函数和数据结构放到不同的文件里,以达到给函数和数据结构分类的目的,照样可以实现代码的模块化。只不过面向对象编程本身提供了类的概念,强制你做这件事情,而面向过程编程并不强求。

实际上,利用面向过程的编程语言照样可以写出面向对象风格的代码,只不过可能会比用面向对象编程语言来写面向对象风格的代码,付出的代价要高一些。而且,面向过程编程和面向对象编程并非完全对立的。很多软件开发中,尽管利用的是面向过程的编程语言,也都有借鉴面向对象编程的一些优点。

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

面向对象编程通过类这种组织代码的方式,将数据和方法绑定在一起,通过访问权限控制,只允许外部调用者通过类暴露的有限方法访问数据,而不会像面向过程编程那样,数据可以被任意方法随意修改。

函数本身就是一种抽象,它隐藏了具体的实现。在使用函数的时候,只需要了解函数具有什么功能,而不需要了解它是怎么实现的。从这一点上,不管面向过程编程还是是面向对象编程,都支持抽象特性。不过,面向对象编程还提供了其他抽象特性的实现方式。这些实现方式是面向过程编程所不具备的,比如基于接口实现的抽象。

继承特性让代码更容易实现复用。

在需要修改一个功能实现的时候,可以通过实现一个新的子类的方式,在子类中重写原来的功能逻辑,用子类替换父类。在实际的代码运行过程中,调用子类新的功能逻辑,而不是在原有代码上做修改。(“对修改关闭、对扩展开放”的设计原则)提高代码的扩展性。利用多态特性,不同的类 对象可以传递给相同的方法,执行不同的代码逻辑,提高了代码的复用性。

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

跟二进制指令、汇编语言、面向过程编程语言(这三者都是一种流程化,面条式的编程风格)相比,面向对象编程语言的编程套路、思考问题的方式,是完全不一样的。前三者是一种计算机思维方式,而面向对象是一种人类的思维方式。

在用前面三种语言编程的时候,我们是在思考,如何设计一组指令,告诉机器去执行这组指令,操作某些数据,帮我们完成某个任务。而在进行面向对象编程时候,我们是在思考,如何给业务建模,如何将真实的世界映射为类或者对象,这让我们更加能聚焦到业务本身,而不是思考如何跟机器打交道。

面向对象编程比面向过程编程,更加容易应对大规模复杂程序的开发。但像 Unix、Linux 这些复杂的系统,也都是基于 C 语言这种面向过程的编程语言开发的,你怎么看待这个现象?这跟我之前的讲解相矛盾吗?