(11) – “封装” 详解
最后更新于:2022-04-01 07:29:05
## 连载:面向对象葵花宝典:思想、技巧与实践(11) - “封装” 详解
封装的概念本身很好理解,意思就是把一堆东东装起来。
但要想真正理解封装,这样还远远不够。
第一个问题是:我们要封装什么?
这个问题很好回答,封装当然是封装不想让别人知道或者看到的东东了。
例如:
你的身家。。。。。。
漂亮MM的年龄。。。。。。
富二代的女朋友数。。。。。
明星是否整过容。。。。。。
你是如何赚到100万的(想想什么样的赚钱方法不想让人知道?)。。。。。。
你是如何消磨时间的(想想什么样的消磨时间方法不想让人知道?)。。。。。。
等等
站在面向对象的角度来说,封装就是“类”的一个功能,你可以封装“类”的属性(比如身家、年龄等),也可以封装“类”的方法(比如说如何赚钱、如何消磨时间),也就是说,面向对象通过“类”来实现了封装。
第二个问题是:我们为什么要封装?
封装数据的主要原因是“**保护隐私**”,因为有“隐私”,所以要“封装”。如果没有封装,你的隐私就暴露在所有人面前了,别人也可以控制你的隐私,那样将是非常危险的。
例如面向过程的设计中,数据结构是公开的,任何能够获取到数据的人都可以随意修改,也可以使用不同的方式修改,如果某个不小心的程序员或者菜鸟或者一个准备离职又心怀不满的开发人员,无意或有意改错了,那么其它依赖这个数据的函数都会受到影响,要么导致业务出错,甚至导致程序崩溃。
而面向对象的类封装了属性后,对属性的修改只能通过类的方法进行,一来不会暴露内部的具体属性,二来对属性的操作都是统一的,不会出现乱改的情况。
封装方法的主要原因是“**隔离复杂度**”。每个类只需要关注自己的负责的功能如何完成即可,如果需要其它类配合,只需要调用类的方法即可,而不需要了解其它类功能的具体的实现。
“隔离复杂度”的例子即使在现实世界中也比比皆是。例如我们夏天常用的空调能够提供制冷功能,我们只要轻轻一按遥控器的按钮,空调就能够开始制冷,但空调究竟是如何制冷的,绝大部分人并不知道,也并不关心。空调封装了制冷的实现过程,对人提供了一个制冷的按钮,人通过这个按钮来启动制冷的过程。
【封装的样例】
简单的这么回答你可能没有什么感觉,但给一个面向对象“封装”和面向过程“无封装”的例子,相信你就很清楚了。
举个简单的例子,假设用程序实现付款这个操作,我们来看面向对象和面向过程的方式。
面向过程 = 算法 + 数据结构
这里的数据结构是公开的,每个地方都可以看到和引用的,而且必须知道,否则面向过程的各个处理流程就没法处理了。
具体代码实现如下:
person.h
~~~
#ifndef PERSON_H_
#define PERSON_H_
typedef struct Person{
char* name; //姓名
int money; //金钱数量
}Person;
#endif /* PERSON_H_ */
~~~
money.c
~~~
#include <stdio.h>
#include <stdlib.h>
#include "person.h"
#define null 0x00
int main(void) {
Person* me = (Person*)malloc(sizeof(Person));
me->name = "华仔";
me->money = 100;
//收银员收银
if( me->money > 50 ){
me->money -= 50; //收银操作时,需要知道Person实际有多少钱,然后直接操作Person的money
printf("%s 付款成功,共 %d 块\n", me->name, me->money);
}
else{
printf("%s 付款失败 \n", me->name);
}
//小偷偷钱
printf("偷了 %s %d 块\n", me->name, me->money);
me->money = 0; //小偷也可以直接知道Person有多少钱,并直接操作Person的money
free(me);
me = null;
return EXIT_SUCCESS;
}
~~~
也就是说,你有多少钱,所有人都知道!
也就是说,你的钱,别人可以随便控制!
就像你去超市买东西,付款的时候,收银员说:把你所有的钱摆在台子上,我从里面拿50.
不用我说你也知道,这当然是一件很恐怖的事情!
面向对象 = 对象 + 交互
但在面向对象的实现里面就不会这样了,你不需要把钱摆出来,你只需要拿出50就可以了,你既不需要担心别人知道你有多少钱(然后实施某种后续动作),也不需要担心别人拿错了(不管是有意的还是无意的)。
具体实现代码如下:
Person.java
~~~
package com.oo.java;
/**
* 普通人
*/
public class Person {
private String _name; //姓名
private Integer _money; //金钱
public String getName() {
return _name;
}
public Boolean pay(Integer money){
//付款的逻辑由Person自己控制,例如判断当前的钱是否够支付
if( money > _money ){
return false;
}
_money -= money;
return true;
}
}
~~~
Cashier.java
~~~
package com.oo.java;
/**
* 收银员
*/
public class Cashier {
public Boolean get(Person person, Integer money){
Boolean result = person.pay(money); //收银员只需要调用Person的pay方法,无需知道Person当前有多少钱
if( result ){
System.out.println(person.getName() + " 付款成功,共 " + money +" 块");
}
else{
System.out.println(person.getName() + " 付款失败");
}
return result;
}
}
~~~
Thief.java
~~~
package com.oo.java;
/**
* 小偷
*/
public class Thief {
public Boolean stole(Person person){
//小偷想直接访问Person的money,但这样做不允许,编译出错
System.out.println("偷了 " + person.getName() +" " + person._money + " 块\n");
person._money = 0;
//虽然小偷这个类语法上也可以直接调用Person的pay方法,
//但Person的pay方法里面可以做很多的校验功能来让这样的调用返回失败
person.pay(50);
}
}
~~~