芥子须弥
----封装
作者:HolyFire
说起面向对象OOP首先就有人大喊着封装
究竟封装是什么呢,为什么要封装?
是的,本来并没有封装,封装是由于人们的需要才产生的,就如同计算机来到人间,编程语言进入你的大脑,自然而然。
在没有封装的时候,人们一样在编程,并没有因为封装的出现使得猿猴变成了人,所以封装没有那么神秘,可怕。
想象一下,我们生活里也有很多封装:食品被放在袋子里,用来防止混入灰尘和昆虫侵蚀;戴上太阳镜,防止紫外线伤害眼睛;钱和贵重物品放在保险柜里,防止心术不正的人。
好了,我们看出一点,封装的一个作用是保护我们的东西。
在生活中,有很多惯例,这里要提到一点,那就永真式,这是一个表示在无论什么情况下都为真的式子,当然这是一种理想的情况,但人们往往喜欢创造这样的东西,就象“水往低处流” 。牛顿说“由于地球引力,所以苹果是往地上掉的”幸亏有这样的道理,我们才有水喝,有苹果吃。当然了在地球上很难找出反例,所以我们无需考虑苹果飞上天的可能性。又如人的听觉范围是20HZ~~20kHZ,如果有人说地球上会有一个人的听觉超出这个范围,我一点异议都没有,但是我还是把人的听觉作为20HZ~~20kHZ来处理,应为概率论上有一句,小概率的事是不会发生的,我不打算证实他,我只是想利用他,我只处理20HZ~~20kHZ的听觉范围,在绝大多数领域里,这样做足够了,让在这方面有特别要求的人挣扎去吧,少处理这些个别的人会带来更多的好处。
我们又能发觉到,封装的好处能降低复杂度。
我想没有人会愚蠢到将一只羊和一只牛相加,得到两只羊或两只牛这样的结果,但是两只动物这样的结果却是可以接受的。
这样看来,封装他能够降低出错的可能性。
封装所做的远远不是这些。
等等,各位心中是不是充满了疑惑。
讲了这么久,为什么不说明封装到底是什么呢,文中并没有提到啊。
聪明的朋友,封装并不是什么东西,也不是什么方法,他是人们考虑和解决问题的思路,他就是你的思想,就是你的灵感一闪,就是你的锦囊妙计。封装就是人们为了更好的管理和使用事物的方案,它可以让你更方便,更安全的做你想做的事,长久以来,充满智慧的杰出者们摸索出一些使用封装来解决问题的办法。
在编程的应用上,我们看看封装给我们带来的好处吧。
由于计算机里,一些都数字化了,所以信息都是存放在很多很多的存储单元里的,由于硬件的限制,这些存储单元都有确实的大小。
首先我们来谈谈保护我们的数据不被随便访问,这正是我提到的。
比如说一个员工可以看到自己的工资,但如果他能够修改的话,只怕人人都想去这个公司工作了。修改工资的数据,万万不行,万万不行,但是如果人人都不能修改工资这一数据,那也不行啊,财会需要修改关于工资的数据。
这里我使用面向对象开发而设计的C++语言来实现他,因为他是为面向对象设计的,所以实现起来比较方便。没学过C++的人先要看一看基本概念,至少要理解private,public,protected,friend等关键字的概念。
我们来理解一下我们要做的事,公司里有很多人,人人都有工资,一些是员工,员工用工号来区别,一些是财会,而财会也是员工,员工能察看工资,而财会还能修改工资。
我们有三个角色,人,员工,财会,要做两件事,员工察看工资,财会修改工资,然后理清他们的关系
人有姓名,年龄,性别 这三样是我们需要处理的,我们要做的是处理工资,一些无关的信息就可以省略,习惯上每个部门都需要人的这三个信息
人 = { 姓名 , 年龄 , 性别 }
员工是人,财会是人,财会是员工,员工有工资,为了区别员工每个员工有工号
员工察看工资,财会修改工资
员工 = { 人 , 工资 ,工号 , 察看工资 }
财会 = { 员工 , 修改工资 }
//下面三句代码是使用标准函数库,可以节省我们很多功夫,方便我们理解我们要理解的,略过繁枝小节
#include <iostream>
#include <string>
using namespace std;
class Man{
public:
enum SexType { Mele , Female }; //枚举类型,性别只有男女两种,这里不考虑人妖,如果输入不是这样种类型,编译器会报警。类型转换中如果不是者两个值,也会报警,这样我们减少了误输入引起的错误。
private: //变量不能乱操作,设定为私有,只有类的成员函数才能操作,起到保护作用
string Name; //姓名,string是标准函数库里提供的类,可以方便的处理字符串
unsigned Age; //年龄,由于年龄不可能是负数,所以用unsigned表示,这样可以防止逻辑错误
SexType Sex; //性别
public: //对外的接口,当然要开放了
string GetName( void ){ return Name; } //得到人的名字
void SetName( string const& name ) //改变人的名字,这里预先检查了名字是否合法
{
if( str == "" )
return;
Name = name;
}
unsigned GetAge( void ){ return Age; } //得到人的年龄
void SetAge( unsigned age ) //改变人的年龄,由于员工50岁就退休了,所以50以下的才合法
{
if( age >= 50 )
return;
Age = age;
}
SexType GetSex( void ){ return Sex; } //得到人的性别
Void SetSex( SexType sex ){ Sex = sex } //改变人的性别
};
class Accountant;
class Employee : public Man{
private:
unsigned ID; //工号
protected: //虽然要加以保护,但是他的后继类财会要操作的
unsigned Pay; //工资
public:
unsigned GetID( void ); //取得工号
void SetID( unsigned ); //改变工号
unsigned GetPay( void ){ return Pay; } //察看工资
friend class Accountant; //由于财会能够修改所有员工的工资,所以要将访问权信托给财会
};
class Accountant : public Employee{
public:
void SetPay( unsigned pay ){ Pay = pay; } //改变自己的工资
void SetPay( Employee * man , unsigned pay ){ man->Pay = pay; }; //改变别人的工资
};
当然这是经过精心设计后的封装,简化了结构,正因为一开始细心的分析,才使得设计可以轻松自如,归根结底是由于思想正确,好了,封装是一种思想,我们现在将他体现了出来。
再看这个例子里,工资的类型是unsigned,非负整数,呵呵,大家都不愿意到工资为负的公司工作吧,这里简化问题是从人们的惯例的角度出发的,如果你的老板考虑工资为负的情况,那么…^_^
现在一个粗心的财会不小心改错了,他多敲了一个0,哇欧,请客请客,但这个财会可就惨了,这样的好事不会发生,不准发生,老板青着脸狂吼着。
设计不得不加上一个工资的上限,没办法啦,现在国家规定的吗。
class Accountant : public Employee{
enum MaxPay{ MAXPAY = 8000 };
public:
void SetPay( unsigned pay )
{
if( pay > MAXPAY )
return;
Pay = pay;
}
void SetPay( Employee * man , unsigned pay )
{
if( pay > MAXPAY )
return;
man->Pay = pay;
}
};
可以看出封装的作用就是减少出错的可能,方便灵活的运用类型
在上面的例子里我们看到,类class是由一些变量和函数组成的,这些变量和函数是类的一部分,我们称之为成员,变量就是成员变量,函数当然就叫成员函数了。为什么要这样呢,我们考虑一下,事物是由物质和运动组成的,表现物质的一面我们通常描述他的一些属性,即他拥有什么,表现一个运动我们通常使用一个过程,要将一个事物的信息描述清楚就需要这两样东西。在长期的实践中程序员达成一个共识,将事物的特性(也就是它拥有的)称之为属性,他能够产生的行为称之为方法,数字化以后就是成员变量和成员函数,他们的组成的整体就是类(类型),这个类型将作为一个单独的节点考虑,就像例子中的Man,我们不会说这是一个姓名,年龄等等的组合体,而是将他作为一个类--class Man考虑,从而简化了问题。细小的事物组合成大的事物,大的事物组合成更大的事物,这样下去,再大难题也可以化作小模块来处理,这正是封装诱人的地方和他的使命。
需要补充的是,既然我们把数据保护起来,那么如何让用户访问这些数据就是一个问题了,在上面的例子中看出public:申明的方法,用户是可以使用的,而我们正是通过这些方法将数据的信息告诉使用者,这里我们将描述方法的部分就叫做接口(在C++里,就是类中成员函数的声明,用户一般只对public:部分的接口感兴趣,所以有人建议将public:部分的内容写在显眼的地方,比如靠类的顶部),也有人说是界面,也就是类和外界沟通和交流使用的渠道,所以接口是很重要的,他直接关系到你的类使用的方面。
而使用者使用类的某个接口的时候就象是通知这个类型使用某个行为,就象是传递一个消息给他一样,我们把使用接口称之为传递消息,而类被调用方法称之为接受消息。
现在我们可以出定义:封装就是将事物的内容和行为都隐藏在实现里,用户不需要知道其内部实现,这是大量程序员反复劳动后得出的一致结论。这样的好处就是使用方便,易于维护,任何一样都可以使程序员为之心动。当然我们不能保证高效,但是不意味着使用封装就没有高效的可能,如果在封装的基础上保证高效的话,我实在找不出理由来拒绝他。
2001/8/12
丁宁