560 likes | 682 Views
面向对象程序设计概述. 程序设计发展历程 类设计的一般概念. 主要内容. 效率至上的年代. 计算机的发明到现在已经 60 年了,计算机程序设计方法也伴随着计算机硬件技术的提高而不断发展。硬件环境对软件设计既有严重的制约作用,也有积极的推动作用。在 70 年代,一台小型机的售价是 10 几万人民币,如果换算到今天,相当于大约 100 多万元,但是就是这样昂贵的小型机,他的内存是多少?答案是 4K 。. 效率至上的年代.
E N D
程序设计发展历程 类设计的一般概念 主要内容
效率至上的年代 • 计算机的发明到现在已经60年了,计算机程序设计方法也伴随着计算机硬件技术的提高而不断发展。硬件环境对软件设计既有严重的制约作用,也有积极的推动作用。在70年代,一台小型机的售价是10几万人民币,如果换算到今天,相当于大约100多万元,但是就是这样昂贵的小型机,他的内存是多少?答案是4K。
效率至上的年代 • 这就是当时的环境。这样的环境下,用什么写程序?当然只有机器码了。先用汇编写,然后翻阅手册手工改写为机器码,然后打卡或穿纸带,输入运行。可以想象,在当时的条件下,什么叫好的程序呢?什么叫优秀的程序?—— 技巧!在这种条件下,谈什么设计方法都是无用的,总之,那是一个效率至上的年代。
结构化程序设计方法 • 随着计算机的价格不断下降,硬件环境不断改善,运行速度不断提升。程序越写越大,功能越来越强,讲究技巧的程序设计方法已经不能适应需求了。记得是哪本书上讲过,一个软件的开发成本是由:程序设计 30% 和程序维护 70% 构成。这只是一个理论值,实际上可能维护所占的比重会更大。
结构化程序设计方法 • 下面给出两个例子,大家来评价一下哪个更好: • 题目:对一个数组中的100个元素,从小到大排序并显示输出。 • 方法1:冒泡法排序,同时输出。
结构化程序设计方法 for( int i = 0; i < 100; ++i ) { for( int j = i + 1; j < 100; ++j ) { if( a[ i ] > a[ j ] ) { int iTemp = a[ i ]; a[ i ] = a[ j ]; a[ j ] = iTemp; } } cout << a[ i ] << “\t”; } cout << endl;
结构化程序设计方法 方法2:先进行排序,然后输出。 for( int i = 0; i < 100; ++i ) { for( int j = i + 1; j < 100; ++j ) { if( a[ i ] > a[ j ] ) { int iTemp = a[ i ]; a[ i ] = a[ j ]; a[ j ] = iTemp; } } } for( i = 0; i < 100; ++i ) { cout << a[ i ] << “\t”; } cout << endl;
结构化程序设计方法 • 显然,“方法1”比“方法2”的效率要高,运行的更快。 • 但是,从现在的程序设计角度来看,“方法2”更高级。原因很简单:(1)功能模块分割清晰——易读;(2)也是最重要的——易维护。 • 程序在设计阶段的时候,就要考虑以后的维护问题。
结构化程序设计方法 比如现在是实现了在屏幕上的输出,也许将来某一天,你要修改程序,输出到打印机上、输出到绘图仪上;也许将来某一天,你学习了一个新的高级的排序方法,由“冒泡法”改进为“快速排序”、“堆排序”。那么在“方法2”的基础上进行修改,是不是就更简单了,更容易了?!这种把功能模块分离的程序设计方法,就叫“结构化程序设计”。当然这个程序更进一步可以定义两个函数Sort()和Print(),用来排序和打印。
面向对象的设计方法 • 随着程序的设计的复杂性增加,结构化程序设计方法又不够用了。不够用的根本原因是“代码重用”的时候不方便。 • 面向对象的方法通过继承来实现比较完善的代码重用功能。面向对象的设计方法和思想,其实早在70年代初就已经被提出来了。
面向对象的设计方法 • 面向对象的目的是:强制程序必须通过函数的方式来操纵数据。这样实现了数据的封装,就避免了以前设计方法中的,任何代码都可以随便操作数据而因起的BUG,而查找修改这个BUG是非常困难的。
面向对象的设计方法 • 我们可以说,即使不使用面向对象,当我们想访问某个数据的时候,通过调用函数访问不就可以了吗? • 是的,的确可以,但并不是强制的。人都有惰性,当我们想对 i 加1的时候,干吗非要调用函数呀?算了,直接i++多省事呀 • 正是由于这个懒惰,当程序出BUG的时候,可就不好捉啦。而面向对象是强制性的,从编译阶段就解决了程序员懒惰的问题。
面向对象的设计方法 • 巧合的是,面向对象的思想,其实和我们的日常生活中处理问题是吻合的。 • 举例来说,如果打算丢掉一个茶杯,怎么扔那?太简单了,拿起茶杯,走到垃圾桶,扔! • 注意分析这个过程,我们是先选一个“对象”------茶杯,然后向这个对象施加一个动作——扔。
面向对象的设计方法 • 每个对象所能施加在它上面的动作是有一定限制的:茶杯,可以被扔,可以被砸,可以用来喝水,可以敲它发出声音......;一张纸,可以被写字,可以撕,可以烧...... • 也就是说,一旦确定了一个对象,则方法也就跟着确定了。我们的日常生活就是如此。
面向对象的设计方法 • 同时,面向对象又能解决代码重用的问题——继承。 • 比如我们编写了一个“狗”的类,属性有(变量):有毛、4条腿、有翘着的尾巴(耷拉着尾巴的那是狼)、鼻子很灵敏、喜欢吃肉骨头......方法有(函数):能跑、能闻、汪汪叫......如果它去抓耗子,人家叫它“多管闲事”。这样,狗这个类就写好了。
面向对象的设计方法 • 但是在实际的生活中,可能某一类狗和前面写的这个“狗类”非常相似,只有一点点的不同,它是:卷毛而且长长的,鼻子小,嘴小...... • 于是,我们可以派生一个新的类型,叫“哈巴狗类”,在“狗类”的基础上,加上新的特性。 • 这样,在程序中,重用了以前的正确的代码——这就是面向对象程序设计的好处。这种成功只是站在了巨人的肩膀上。 • 如果你使用VC的话,重用最多的代码就是MFC的类库。
面向对象的设计方法 • 有了面向对象程序设计方法,就彻底解决了代码重用的问题了吗?答案是否定的!现在硬件越来越快,越来越小了,软件的规模却也越来越大了,集体合作越来越重要,代码重用又出现的新的问题。 • 具体表现在1、用C++写的类,不能被其它语言重用(不能跨语言);2、重用代码只能处于源代码级而不是二进制级,这样就会暴露更多的细节,违背了封装性原则;3、一个类的设计人员走了,那他留下的不是一笔财富,而是一堆非常难于维护的代码......COM的设计方法,就是解决上面问题的一个方式 (本讲义中不会涉及COM的知识 )
类设计的一般概念 • 1. 类代表一个概念或实体,即使非程序员也能理解得了这些概念与实体 类是一个对象,它实际上就是对现实生活中物品的模拟,在类的设计中,越接近现实生活的设计越容易被理解。
类设计的一般概念 • 要保证这点,首先类的名字一定要有它的含义。例如我们要定义一个类来表示“虎”,那么对于这个类的名字,我们定义为ABC就非常不合适了,可能对于类的设计人员知道“ABC==虎”(当类多了之后,有可能连他自己也不清楚了),但对于其他人来讲,必须通过研究这个类的实现细节以及他的属性才能知道这是一个“虎”类。 • 其次,对于某个确定下来的类,它所拥有的属性和方法应该符合它本身的真实特性。
类设计的一般概念 class CTiger { public: void Run(); //虎可以跑 void Jump(); //虎可以跳 void Fly(); //虎可以飞???这将给 类的使用者带来困惑 protected: int m_iWeigh; }; • 也许你说,你所描述的虎是动画或者是神化故事中的虎,它当然可以飞,那么,这时候你应该考虑到继承
类设计的一般概念 class CTiger { public: void Run(); //虎可以跑 void Jump(); //虎可以跳 ...... //普通虎的一些动作 protected: int m_iWeigh; }; class CHeroTiger : public CTiger { public: void Fly(); //神化中的虎可以飞 ...... //神化中的虎的其它特殊技能 };
类设计的一般概念 2. 尽量使类简单,使它能够被一般的程序员所了解 在C的程序设计中,会要求尽量函数设计的简单、清晰,一个函数完成的功能尽可能的单一。 对于C++程序设计,这一条规则同样适用,参看下面的例子,它表示一个Computer类:
类设计的一般概念 class CComputer { public: const int GetCPUType( int *piCPUType ) const; const int GetCPUFrequency( int *piCPUFrequency ) const; const int GetCPUPrice( int *piCPUPrice ) const; const int GetMonitorType( int *piMonitorType ) const; const int GetMonitorSize( int *piMonitorSize ) const; const int GetMonitorPrice( int *piMonitorPrice ) const; ......
类设计的一般概念 private: int m_iCPUType; //CPU型号 int m_iCPUFrequency; //CPU主频 int m_iCPUPrice; //CPU价格 int m_iMonitorType; //显示器型号 int m_iMonitorSize; //显示器尺寸 int m_iMonitorPrice; //显示器价格 ...... //计算机其它零件的属性 };
类设计的一般概念 • 在上面的Computer类中,每一个零件的每一个属性都作为类的属性单独存在,这样给人的感觉非常凌乱。并且,对于某些计算机,有一些零件并不是必须的(比如说某些服务器根本不需要显示器)。 • 这样,如果需要对Computer进行定制的话,就要将某个不需要的零件的属性一个一个的去掉,这将是一件非常繁琐的工作,尤其在这些属性排列不规则的时候。 • 这时候,我们需要将类进行细化。
类设计的一般概念 class CCPU { public: const int GetType( int *piType ) const; const int GetFrequency( int *piFrequency ) const; const int GetPrice( int *piPrice ) const; private: int m_iType; int m_iFrequency; int m_iPrice; };
类设计的一般概念 class CMonitor { public: const int GetType( int *piType ) const; const int GetFrequency( int *piFrequency ) const; const int GetPrice( int *piPrice ) const; private: int m_iType; int m_iFrequency; int m_iPrice; };
类设计的一般概念 class CComputer { public: const int GetCPU( CCPU *pCPU ) const; const int GetMonitor( CMonitor *pMonitor ) const; ...... private: CCPU m_CPU; //CPU CMonitor m_Monitor; //显示器 ...... //计算机其它零件的属性 }; • 这样的一个类结构将使结果看起来清晰的多,并且也更接近于实际情况,毕竟组装机更加物美价廉。
类设计的一般概念 3.充分利用封装性来增加类自身的可靠性,使用起来才能更加可靠 1)封装全局变量 如果有人问我什么样的Bug最难查,我可能回答不出,因为难查的Bug实在太多了,但是和全局变量有关的Bug绝对是最难查的Bug之一。在全局变量满天飞的年代,你可能根本分辨不出那些地方对变量进行了修改(也许有些修改根本是无心)。
类设计的一般概念 如果出现了一个问题,你必须将所有用到全局变量的地方都设上一个断点,不停的F9,F9......,多么庞大的工作量!当然,你可以将所有全局变量封装起来,但你必须做一个人为的规定使所有人都只能从固定的接口进行数据的读写操作,这是一个考验编程素养的事情。 其实,在C++年代,你完全可以将这些规定交给编译器去做:
类设计的一般概念 class CGlobalDataSet { public: CGlobalDataSet(); //初始化所有的全局变量 void GetGlobalData1( int *piData1 ) const; void SetGlobalData1( const int &iData1 ); void GetGlobalData2( int *piData1 ) const; void SetGlobalData2( const int &iData2 ); private: int m_iGlobalData1; //全局变量1 int m_iGlobalData2; //全局变量2 };
类设计的一般概念 • 在上面这个例子里面,将全局变量封装在一个类里面,并且作为类的私有成员存在,这样做的好处: • 所有全局变量被定义为私有,可以避免无意间更改全局变量,因为你无法直接访问到它。 • 变量的读/写操作都有唯一的接口,你不可能在读的时候对变量进行更改,同样你也不可能在写变量的时候取得变量的值,这一点由编译器保证。 • 因为读/写有唯一的接口,也使排错更加容易 • 注:对于全局变量的读/写操作,在实现的时候应该注意进行保护
类设计的一般概念 2)封装实现细节 对于普通的类(相对于上面提到的单纯封装数据的类),也应该在设计的时候考虑到封装性,将没有必要暴露给外面的属性或者方法定义为Private,同时定义一些共有的接口和外部进行交互,将类本身的实现细节隐藏起来。
类设计的一般概念 class CApple { public: void GetCost( int *piCost ) const; void GetWeigh( int *piWeigh ) const; void GetColor( int *piColor ) const; private: int m_iColor; //色泽 int m_iWeigh; //重量 int m_iCost; //售价 int m_iPrimeCost; //成本价 int m_iPlantingTech; //栽培技术 };
类设计的一般概念 对于一个苹果来讲,色泽、重量、售价、成本价以及栽培技术都是它的属性,但是对于前三个属性,我们可以也必须让买家知道,所以对这三个属性提供了对外的接口,以供用户选择。 但是对于成本价以及栽培技术,这可能都是商业秘密,不应该暴露给客户知道。
类设计的一般概念 关于多态性以及更多的细节,我们将在以后 学习……
类设计的一个实例 下面我们将看到一个面向对象设计的另一个例子……
需求陈述 • 在显示器荧光屏上圆心坐标为(100,100)的位置画一个半径为40的圆; • 在圆心坐标为(200,300)的位置画一个半径为20的圆; • 在圆心坐标为(400,150)的位置画条弧,弧的起始角度为30度,结束角度为120度,半径为50。
面向对象分析(1) • 确定对象:名词短语(候选者) • 显示器荧光屏:一种输出设备,不是对象 • 圆心坐标:圆和弧的基本属性,不是对象 • 位置:即圆心坐标,不是对象 • 半径:圆和弧的基本属性,不是对象 • 圆(Circle):对象 • 弧(Arc):对象 • 起始角度:弧的属性,不是对象 • 结束角度:弧的属性,不是对象
面向对象分析(2) • 确定属性: • 确定对象的过程中发现: • 圆的属性:圆心坐标,半径 • 弧的属性:圆心坐标,半径,起始角度,结束角度 • 借助于领域知识: • 圆和弧的属性:可见性
面向对象分析(3) • 确定服务: • 访问属性值: • 读/写圆心坐标(圆,弧) • 读/写半径(圆,弧) • 读/写起始角度(弧) • 读/写结束角度(弧) • 读/写可见性(圆,弧) • 显示(圆,弧) • 隐藏(圆,弧)
面向对象设计 • 建立类层次:圆为父类,弧为子类 • 进一步分析:点,位置 • 定义属性 • 定义服务 • 类图
Implementation in C++ • 自定义数据类型 enum Boolean {false, true}; • 定义类 • 说明数据成员 • 说明和定义成员函数 • 成员函数 • 构造函数
完整的C++程序(1) # include <graphics.h> # include <conio.h> enum Boolean{false, true}; class Location{ protected: int x; int y; public: Location(int InitX, int InitY); int GetX(); int GetY(); };
完整的C++程序(2) class Point: public Location{ protected: Boolean Visible; public: Point(int InitX, int InitY); void Show(); void Hide(); Boolean IsVisible(); };
完整的C++程序(3) class Circle: public Point{ protected: int Radius; public: Circle(int InitX, int InitY, int InitRadius); void Show(); void Hide(); int GetRadius(); };
完整的C++程序(4) class Arc: public Circle{ private: int StartAngle; int EndAngle; public: Arc(int InitX, int InitY, int InitRadius, int InitStartAngle, int InitEndAnagle); void Show(); void Hide(); int GetStartAngle(); int GetEndAngle(); };
完整的C++程序(5) Location::Location(int InitX, int InitY) { x=InitX; y=InitY; } int Location::GetX() { return x; } int Location::GetY() { return y; }
完整的C++程序(6) Point::Point(int InitX, int InitY): Location(InitX, InitY) { Visible=false; } void Point::Show() { Visible=true; putpixel(x, y, getcolor()); } void Point::Hide() { Visible=false; putpixel(x, y, getbkcolor()); }