官方语言

  我们先用官方语言列出面向对象的三大特性:

  1. 封装:封装是将过程和数据包围起来,数据只能通过定义的接口访问。面向对象计算从一个基本概念开始,即现实世界可以表示为一系列完全自治的、封装的对象,这些对象通过受保护的接口访问其他对象。
  2. 继承:继承是一种层次模型,它连接类,允许并鼓励类的重用,提供了一种明确表达共性的方法。对象的新类可以从现有类派生,这个过程称为类继承。新类继承原类的属性。新类被称为原类的派生类(子类),原类被称为新类的基类(父类)。
  3. 多态:多态允许不同类的对象响应相同的消息。例如,同样的加法,两次相加和两个整数相加,一定是完全不同的。多态语言具有灵活性、抽象性、行为共享性和代码共享性等优点,较好地解决了应用程序功能的同名问题。

前言

  很多童鞋看到面向对象的时候完全不知道这是什么,事实上,大部分初次接触面向对象时都无法理解面向对象的思想。不过也不用为此担心,我当初学习编程时也不懂什么是面向对象,甚至连面向对象这个词都不知道,只是按照自己的想法写代码,直到后来有人问我面向对象与面向过程的区别是什么的时候我才开始了解什么是面向对象。不过那个时候我已经掌握基本的开发方法了。

  说这些是想告诉各位,就算不懂面向对象其实也可以面向对象开发,只不过刚开始只能照猫画虎,画的多了自然就会运用面向对象了。但是不论早晚,肯定是需要明白什么是面向对象的,这可以给我们带来新的视角,扩展我们的视野。

什么是面向对象

  面向对象是一种软件开发思想,它与面向过程开发有着根本的区别,但是严格来说,面向对象中也一定会用到面向过程。按照惯例,我们来举个栗子:

  假如我们要编写一个贪吃蛇,我们应该如何设计代码?

面向过程

  如果我们用面向过程的思想,我们会这样设计:

  1. 生成地图
  2. 开始游戏
  3. 绘制画面
  4. 读取用户输入
  5. 移动玩家
  6. 计算玩家移动的后果(无后果返回3,吃到苹果进入7,撞到物体进入10
  7. 增加玩家长度
  8. 生成新的苹果
  9. 返回3
  10. 游戏结束

面向对象

  现在我们使用面向对象的思想来设计这个程序:

  1. 用一个类来表示地图
  2. 用一个类来表示玩家
  3. 用一个类来表示地图元素(包括墙壁、苹果)
  4. 编写main

  我们会发现,当使用面向过程的思想时,我们编写程序是站在计算机的角度看代问题的,我们需要考虑如何实现程序中的所有细节。而我们使用面向对象的思想时,我们是站在人的角度看待问题的,我们只需要思考程序中需要哪些元素,然后最后将这些元素拼接起来就可以了。

封装

  现在,我们来看看面向对象的第一个特性——封装。

  封装就是类向外部隐藏类中的具体实现,这样在外部使用这个类的时候并不需要管这个类是如何实现的,只需要直到这个类提供什么功能就可以了。

  打个比方,现在我们使用QQ在网上和网友聊天。如果我们生活在面向过程的世界中,那么我们在网络上就完全没有隐私可言,谁都可以随意地查看我们的所有的隐私(包括但不限于:身份证号、银行卡号及密码……)。因为面向过程中基本没有封装,想要实现封装只能在函数中使用static关键字,但是这么声明的变量只在一个函数内可见。

  抛开隐私不谈,在面向过程的世界中还存在一个问题。假如我们要统计查看我们信息的人数,我们应该怎么做呢?答案是非常艰难,因为别人在查看信息的时候并不需要告诉我们,他们自己就能直接查看。

  这些情况都太糟糕了,于是我们决定搬到面向对象的世界生活。现在,我们在网上聊天时就不用担心对方随心所欲的查看自己的隐私了,因为我们把我们的信息都加上了保护措施(publicprotectedprivate……)。当我们想统计有多少人查看了自己的信息时也十分方便,因为别人只有通过我们才能查看我们的信息,这样子我们就能记录下有谁查看了我们的信息。

继承

  接着,我们来看面向对象的第二个特性——继承。

  继承指的是两个类之间的关系,表明其中一个类“继承”了另一个类的属性。我们将继承关系中的被继承者称为“父类”,另一个则相应的称之为“子类”。

  再打个比方,现在我们想要和一个人聊天,在面向过程的世界中,所有东西都是数据的集合体,相互之间没有任何关联。所以我们不能简单的说“我要和一个人聊天”,我们需要通过人的特征去寻找可以和我们聊天的“人”,于是乎,我们说:“我要和一个会说中文的东西聊天。”但是这么找到的东西一定是人吗?答案肯定是否定的,符合这个条件的东西太多了,鹦鹉、音响……都可以满足我们的条件。想要100%找到人聊天我们需要添加更多的限定,比如说“两手两足、直立行走、能够思考……”,但是这么做实在是太麻烦了。

  现在我们又来到了面向对象的世界,当我们想要和一个人聊天时,我们只需要说:“我要和一个从‘人’继承的东西和我聊天。”从继承的东西有可能不是人吗?答案是否定的,从继承的东西一定符合所有的特征。是不是简单了许多?

  现在我们用代码解释一下上面的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Man {
//男人具有的属性
}

struct Woman {
//女人具有的属性
}

//我们无法用一个类型同时表示Man和Woman,所以只能使用一个void*来接收参数,
//这时候如果不在注释和文档中说明要传入什么参数就会让使用者一头雾水,不知道该传入什么。
//
//想要解决这个问题也不是没有办法,就是把所有人的属性都装进一个结构体中,
//这样就能用一个数据类型表示所有人了,但是问题就是这会让这个结构体变得相当复杂。
char* chat(void* own, void* person, char text[]) {
//do something...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {

//所有人都具有的属性

//使用面向对象的话我们只需要让所有人都从Person类派生即可
public String chat(Person person, String text) {
//do something...
}

}

class Man extends Person {
//男人具有的属性
}

class Woman extends Person {
//女人具有的属性
}

  一个类从父类继承的不只是类型,同时还有其中的属性。比如父类中声明了int getAge()的方法,那么子类中同样也会有这个方法。

多态

  最后,我们来说面向对象的第三个特性——多态。

  多态就是同一个接口,使用不同的实例而执行不同操作。

  再来打个比方,假如你是一个老师,你要在课堂上进行提问,台下的学生就是堆中的对象,而手中的花名册就是我们存储的对象的引用。

  现在,我们要提问TJ同学:“你一共旷了几节高数课?你对此有什么想法?”然后我们又提问其它同学同样的问题。显然每个同学都会有不一样的反应,这就是我们所说的多态。“同学”都从“人”中继承了所有属性,并且重写了被提问的方法,从而实现每个人都有自己独有的反应。

  用代码表现出来就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public class Main {

public static void main(String[] args) {
Teacher test = new Teacher("kmar", 200, true);
List<Person> students = Arrays.asList(
new Student("TJ", 100, false, 2021, 11),
new Student("ZYZ", 150, true, 2020, 22),
new Student("ZHY", 100, true, 2020, 33)
);
Random random = new Random();
//提问的人数
int amount = random.nextInt(students.size() >> 1) + 1;
Set<Integer> record = new LinkedHashSet<>();
while (record.size() != amount) {
int dist;
do {
dist = random.nextInt(students.size());
} while (record.contain(dist));
record.add(dist);
System.out.println(
"学生[" + student[dist].toString() + "]的回答是:"
+ test.quiz(students[dist], "你一共旷了几节高数课?你对此有什么想法?"));
}
}

}

class Person {

private final String name;
private int age;
/** 性别,true为男,false为女 */
private final bool sex;

protected Person(String name, int age, bool sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public bool isMan() {
return sex;
}

public int plusAge() {
return ++age;
}

}

interface IStudent {

/**
* 回答问题
* @param questioner 提问者
* @param question 问题内容
* @return 回答
*/

String reply(Person questioner, String question);

}

class Teacher extends Person {

public Teacher(String name, int age, bool sex) {
super(name, age, sex);
}

/**
* 提问问题
* @param student 被提问的学生
* @param question 提问的问题
* @return 学生的回答
*/

public String quiz(IStudent student, String question) {
return student.reply(this, question);
}

}

class Student extends Person implements IStudent {

/** 所在班级编号 */
private int classCode;

/**
* 构建一个学生
* @param name 姓名
* @param age 年纪
* @param sex 性别
* @param grade 年级
* @param clazz 班级(0~99)
*/

public Student(String name, int age, bool sex, int grade, int clazz) {
super(name, age, sex);
classCode = grade * 100 + clazz;
}

public int getClassCode() {
return classCode;
}

@Override
public String reply(Person questioner, String question) {
//do something...
//这里写的就是每个人对问题的处理
//这个代码直接编译是会编译错误的,因为不存在answer这个变量
return answer;
}

@Override
public String toString() {
return String.format("姓名:%s;年龄:%d;性别:%b;班级:",
getName(), getAge(), isMan(), getClassCode());
}

}