目录:
三大编程范式
函数式编程
面向过程编程
面向对象编程
面向对象编程的优点
适合大规模复杂项目
模块化组织代码
封装、抽象、继承、多态
面向对象语言丰富
指导落地案例丰富
封装、继承、多态、抽象
封装
抽象
继承
多态
写这个的原因
三大编程范式
函数式编程
![a0545b85015a501431d3a4495b4e8359.png](https://i-blog.csdnimg.cn/blog_migrate/f8d7f4e445113faef114590195eadcac.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");
}
}
需要三个类,一个是实体类,一个用户文件解析类,一个主类。
面向对象风格的代码被组织成一组类
,方法和数据结构被绑定一起,定义在类中。
面向对象编程的优点
他们从外观看上去,只有代码组织方式不同的感觉,面向对象的价值在哪里?
适合大规模复杂项目
面向对象编程更加适合大规模复杂项目开发。
对于大规模复杂程序的开发,程序的处理流程并非简单的一条流水线,而是错综复杂的网状结构。
面向对象编程之前会先面向对象分析和面向设计,分析如何给业务建模,如何将需求翻译为类,如何给类之间建立交互关系,而完成这些工作完全不需要考虑错综复杂的处理流程。
当我们有了类的设计之后,然后再像搭积木一样,按照处理流程,将类组装起来形成整个程序。这种开发模式、思考问题的方式,能让我们在应对复杂程序开发的时候,思路更加清晰。
模块化组织代码
面向对象编程提供了一种更加清晰的、更加模块化的代码组织方式--类。
比如,我们开发一个电商交易系统,业务逻辑复杂,代码量很大,可能要定义数百个函数、数百个数据结构,那如何分门别类地组织这些函数和数据结构,才能不至于看起来比较凌乱呢?
类就是一种非常好的组织这些函数和数据结构的方式,是一种将代码模块化的有效手段。
当然,像 C 语言这种面向过程的编程语言,我们也可以按照功能的不同,把函数和数据结构放到不同的文件里,以达到给函数和数据结构分类的目的,照样可以实现代码的模块化。是这样!
只不过面向对象编程本身提供了类的概念,强制你做这件事情,而面向过程编程并不强求。不强求的话,每个程序员就可以放飞自我自由发挥了,试想下如果有上百万行代码的项目,这结果......有时候太多的自由反而不利于建议标准,这也算是面向对象编程相对于面向过程编程的一个微创新吧。
封装、抽象、继承、多态
面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。
这一点可能有点抽象,下面我会依次展开。
面向对象语言丰富
面向对象编程语言丰富,语言越高级使用方式就越低级,程序员的学习门槛越低,面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。
这一点,从我上图中,面向对象编程代表语言:Java、C++、Go、Python、C#、Ruby、JavaScript、Objective-C、Scala、PHP、Perl 就可看出。
指导落地案例丰富
无数成熟落地项目证明,面向对象编程是可以指导落地、并且能抗住长期需求迭代的编程方式。
封装、继承、多态、抽象
封装、继承、多态、抽象,这四个就是面向对象编程的四驾马车,是指导面向对象语言发展的指导思想,是语言无关的,大多数的高级语言几乎都实现了这四个特性,就算没有直接写语法支持,也可以间接支持。
我想强调一点的是,这四驾马车的语言产品,虽然不同语言语法不同,但是这四个特性是都可以实现的。先有面向对象编程的四大特性,再有语言产品。
封装
封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。
对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制
。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](https://i-blog.csdnimg.cn/blog_migrate/6cf0e95800be36c0dd3dfb8f3ddf7573.png)
最近的工作,使得我阅读了大量代码,发现有些代码设计的真实精妙,自己肯定写不来这种代码,让我设计,我应该怎么写?然后脑海中又浮现出一句主管经常说的话"不要在这里生产垃圾",内心甚是惶恐,于是学习设计模式的优先级从学习列表排期中跃然成为第一位。
理解面向对象是理解设计模式的第一位,毕竟设计模式都是前辈们在面向对象编程中总结出来的设计方式,在无数代码中提炼出来的精华。因此今年自己的输出将会侧重在设计模式相关的内容,打算写一个系列,欢迎持续关注。
参考:王争老师设计模式之美,《设计模式之禅》
❝往期精彩推荐:
❞
开发必会的测试知识,Junit+Mock+Assert+DevOps
Kong 优雅实现微服务网关鉴权,登录场景落地实战篇
一文弄懂什么是DevOps