设计模式之"你低估了的面向对象"编程

目录:

  • 三大编程范式

    • 函数式编程

    • 面向过程编程

    • 面向对象编程

  • 面向对象编程的优点

    • 适合大规模复杂项目

    • 模块化组织代码

    • 封装、抽象、继承、多态

    • 面向对象语言丰富

    • 指导落地案例丰富

  • 封装、继承、多态、抽象

    • 封装

    • 抽象

    • 继承

    • 多态

  • 写这个的原因

三大编程范式

函数式编程

a0545b85015a501431d3a4495b4e8359.png
编程范式

这张图我总结了面向对象编程、面向过程编程、以及函数式编程最核心的内容点,看完差不多就能明白他们三者之间的核心内容了。

鉴于上面的概念性总结还是有点抽象,这里我想给出例子,最直接的感受下面向对象编程风格和面向对象编程风格的不同:

举个栗子:假设我们有一个记录了用户信息的文本文件 users.txt,每行文本的格式是 name&age&gender(比如,小曾&24&女)。我们希望写一个程序,从 users.txt 文件中逐行读取用户信息,然后格式化成 "name"\t"age"\t"gender"(其中,\t 是分隔符)这种文本格式,并且按照 age 从小到大排序之后,重新写入到另一个文本文件 formatted_users.txt 中。

面向过程编程

「C语言实现的面向过程编程风格」

struct User {
  char name[64];
  int age;
  char gender[16];
};

struct User parse_to_user(char* text) {
  // 将text(“小曾&24&女”)解析成结构体struct User
}

char* format_to_text(struct User user) {
  // 将结构体struct User格式化成文本("小曾\t24\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/zeng/user.txt", "/home/zeng/formatted_users.txt");
}

面向过程风格的代码被组织成了一组方法集合及其数据结构(struct User)

面向对象编程

「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(“小曾&24&女”)解析成类User
  }
  
  public String formatToText() {
    // 将类User格式化成文本("小曾\t24\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(String[] args) {
    UserFileFormatter userFileFormatter = new UserFileFormatter();
    userFileFormatter.format("/home/zeng/users.txt", "/home/zeng/formatted_users.txt");
  }
}

需要三个类,一个是实体类,一个用户文件解析类,一个主类。

面向对象风格的代码被组织成一组类,方法和数据结构被绑定一起,定义在类中。

面向对象编程的优点

他们从外观看上去,只有代码组织方式不同的感觉,面向对象的价值在哪里?

适合大规模复杂项目

  1. 面向对象编程更加适合大规模复杂项目开发。

对于大规模复杂程序的开发,程序的处理流程并非简单的一条流水线,而是错综复杂的网状结构。

面向对象编程之前会先面向对象分析和面向设计,分析如何给业务建模,如何将需求翻译为类,如何给类之间建立交互关系,而完成这些工作完全不需要考虑错综复杂的处理流程。

当我们有了类的设计之后,然后再像搭积木一样,按照处理流程,将类组装起来形成整个程序。这种开发模式、思考问题的方式,能让我们在应对复杂程序开发的时候,思路更加清晰。

模块化组织代码

  1. 面向对象编程提供了一种更加清晰的、更加模块化的代码组织方式--类。

比如,我们开发一个电商交易系统,业务逻辑复杂,代码量很大,可能要定义数百个函数、数百个数据结构,那如何分门别类地组织这些函数和数据结构,才能不至于看起来比较凌乱呢?

类就是一种非常好的组织这些函数和数据结构的方式,是一种将代码模块化的有效手段。

当然,像 C 语言这种面向过程的编程语言,我们也可以按照功能的不同,把函数和数据结构放到不同的文件里,以达到给函数和数据结构分类的目的,照样可以实现代码的模块化。是这样!

只不过面向对象编程本身提供了类的概念,强制你做这件事情,而面向过程编程并不强求。不强求的话,每个程序员就可以放飞自我自由发挥了,试想下如果有上百万行代码的项目,这结果......有时候太多的自由反而不利于建议标准,这也算是面向对象编程相对于面向过程编程的一个微创新吧。

封装、抽象、继承、多态

  1. 面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。

这一点可能有点抽象,下面我会依次展开。

面向对象语言丰富

  1. 面向对象编程语言丰富,语言越高级使用方式就越低级,程序员的学习门槛越低,面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。

这一点,从我上图中,面向对象编程代表语言:Java、C++、Go、Python、C#、Ruby、JavaScript、Objective-C、Scala、PHP、Perl 就可看出。

指导落地案例丰富

  1. 无数成熟落地项目证明,面向对象编程是可以指导落地、并且能抗住长期需求迭代的编程方式。

封装、继承、多态、抽象

封装、继承、多态、抽象,这四个就是面向对象编程的四驾马车,是指导面向对象语言发展的指导思想,是语言无关的,大多数的高级语言几乎都实现了这四个特性,就算没有直接写语法支持,也可以间接支持。

我想强调一点的是,这四驾马车的语言产品,虽然不同语言语法不同,但是这四个特性是都可以实现的。先有面向对象编程的四大特性,再有语言产品。

封装

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。

对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。private、public 等关键字就是 Java 语言中的访问权限控制语法。

封装价值自由也意味着不可控,如果属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。不满足设计模式提倡的高内聚。

抽象

封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

就好比,客户买电子产品,只需要提供一个使用说明书即可,没必要将电子产品具体的实现原理,底层知识告知,用户只关心功能使用,有时候知道的越多对使用者反而是一种负担。

在面向对象编程中,我们常借助编程语言提供的接口类(比如 Java 中的 interface 关键字语法)或者抽象类(比如 Java 中的 abstract 关键字语法)这两种语法机制,来实现抽象这一特性。

ps:抽象有时候会被排除在面向对象的四大特性之外,是因为抽象这个概念是一个非常通用的设计思想,并不单单用在面向对象编程中,也可以用来指导架构设计等。而且这个特性也并不需要编程语言提供特殊的语法机制来支持,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性、所以,它没有很强的“特异性”,有时候并不被看作面向对象编程的特性之一。

抽象价值很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。用好了这些设计原则,我们的代码会变得非常灵活。

继承

继承最大的一个好处就是代码复用。假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。

不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还需要按照继承关系一层一层地往上查看“父类、父类的父类……”的代码。因此你可能听说过”多用组合少用继承“ 这种说法,有机会再展开说说。

多态

「多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。」 多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。

利用“继承加方法重写”方式实现多态

个人觉得这个是最难理解的,我学生期间对于这个理解一直是半懂状态。网上最多的栗子是通过继承的方式,子类继承父类,重写父类方法,然后父类引用指向子类对象,执行时实际上运行子类的实现,达到一个同一个方法在执行时候表现出来不同的形态的效果,巧妙的将子类替换成了父类。这种实现需要语言满足三个特性:1. 继承 2. 重写 3.父类引用指向子类对象。这个例子我不写实现,网上几乎都是这种例子

利用"接口类"来实现多态特性
public interface Iterator {
  boolean hasNext();
  String next();
  String remove();
}

public class Array implements Iterator {
  private String[] data;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法...
}

public class LinkedList implements Iterator {
  private LinkedListNode head;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法... 
}

public class Demo {
  private static void print(Iterator iterator) {
    while (iterator.hasNext()) {
      System.out.println(iterator.next());
    }
  }
  
  public static void main(String[] args) {
    Iterator arrayIterator = new Array();
    print(arrayIterator);
    
    Iterator linkedListIterator = new LinkedList();
    print(linkedListIterator);
  }
}

我们利用多态的特性,仅用一个 print() 函数就可以实现遍历打印不同类型(Array、LinkedList)集合的数据。当再增加一种要遍历打印的类型的时候,比如 HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next() 等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性

如果我们不使用多态特性,我们就无法将不同的集合类型(Array、LinkedList)传递给相同的函数(print(Iterator iterator) 函数)。我们需要针对每种要遍历打印的集合,分别实现不同的 print() 函数,比如针对 Array,我们要实现 print(Array array) 函数,针对 LinkedList,我们要实现 print(LinkedList linkedList) 函数。而利用多态特性,我们只需要实现一个 print() 函数的打印逻辑,就能应对各种集合数据的打印操作,这显然提高了代码的复用性

除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。

总之,封装隐藏属性,抽象隐藏方法,继承支持复用,多态支持扩展

写这个的原因

评判代码好坏的标准有哪些? 除了少些bug,还有灵活性、可扩展性、可维护性、可读性、可理解性、易修改性、可复用、可测试性、模块化、高内聚低耦合、高效、高性能、安全性、兼容性、易用性、整洁、清晰、简单、健壮性、鲁棒性......大多数人还停留在"代码和我,有一个能跑就行"的阶段......

0f005b91652ba6cebea518abc20243aa.png

最近的工作,使得我阅读了大量代码,发现有些代码设计的真实精妙,自己肯定写不来这种代码,让我设计,我应该怎么写?然后脑海中又浮现出一句主管经常说的话"不要在这里生产垃圾",内心甚是惶恐,于是学习设计模式的优先级从学习列表排期中跃然成为第一位。

理解面向对象是理解设计模式的第一位,毕竟设计模式都是前辈们在面向对象编程中总结出来的设计方式,在无数代码中提炼出来的精华。因此今年自己的输出将会侧重在设计模式相关的内容,打算写一个系列,欢迎持续关注。

参考:王争老师设计模式之美,《设计模式之禅》

往期精彩推荐:
开发必会的测试知识,Junit+Mock+Assert+DevOps

Kong 优雅实现微服务网关鉴权,登录场景落地实战篇

一文弄懂什么是DevOps

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值