面向对象和结构化方法的比较
最近又有客户问起结构化方法(即结构化分析SA Structured Analysis 和结构化设计SD Structured Design)和面向对象分析设计(OOAD Object-Oriented Analysis & Design)方法的区别,这是一个很多人谈了很多遍的问题,Google一下就可以找到很多以此为标题的文章。OO技术发展了很多年了,现在大家都在用,已经没有什么异议了,几乎没有人会怀疑这种技术的好处。但它明显是一位过气的明星,就连前几年才出道的模型驱动开发(MDD)也已经过了风头最劲的时段,我们现在更热衷于讨论一些更加时尚的概念如:SOA、IT治理、循规等。
要想了解结构化方法和面向对象方法,去看一下 Roger S.Pressman 写的"软件工程:实践者的研究方法(Software Engineering: A Practitioner's Approach)"(这是一本很流行的书,网上就有卖)就可以了,里面有专门的章节介绍这两种方法及其区别。
我喜欢把软件系统描述成对现实世界的映射,现实世界中的我去ATM机取了100元钱,映射到软件就是从我的银行帐户对应的数据库记录中余额(Balance)那一字段减去100。所谓的结构化方法和面向对象方法,就是两种不同的映射手段,结构化方法是以处理过程为中心,强调先定义数据结构(ER实体关系建模),然后分析处理逻辑(DFD数据流图);面向对象方法则主张两者之间的自然映射,在ATM取款的例子中,我被映射为Customer对象,我的帐户被映射为Account对象(在对象-关系映射中再对应到数据库表Account中的一条数据记录)。正因为OO方法采用对现实世界的自然映射,现实世界中的业务流程发生变化时,软件实现也可以比较方便地跟着转变;而采用结构化方法的映射(或是采用OO方法,但对业务流程的映射关系建立不当),软件上相应修改的工作量就会大一些。
应当说明的是这两种开发方法之间并不是一个完全对立的关系,结构化方法出现在前,并且得到了很成功的应用;面向对象方法诞生在后,说它是从结构化方法发展而来也不为过,它也继承了很多结构化方法中的成功经验如:数据抽象、自顶向下、模块化、高内聚、低耦合等,我们应该把OO技术看作是软件设计方法的最佳实践经验整合。结构化方法在过去也很成功,但是我们所开发的软件规模越来越宠大,软件系统越来越复杂,20年以前开发一个软件可能是100%从头开发的,现在开发一个软件可能只有10~20%的代码是新开发的,很多功能尤其是基础功能都是可以重用的。J2EE技术就是一个典型的例子,它把很多基础功能如会话(Session)管理、事务(Transaction)管理、对象关系映射(O-R Mapping)等都已经在中间件中实现了,你只需要重用它们就可以了。要提高软件重用性的话,就一定要引入OO方法中的一些关键理念如:封装、多态、抽象层次结构等,这些机制可以提高软件的可重用度,帮助我们有效地管理软件系统日益增长的复杂度。
实际上结构化方法和面向对象方法之间并不是革命性的变化,我把它们比喻为“走路”和“跑步”的区别,原始人为了捕捉猎物,必须跑步前进;跑步跟走路的区别不是很大,仅仅是迈步频率加快,并且双脚可能同时离地而已。而模型驱动开发(MDD)则是一种革命性的变化,我把它比喻成“骑自行车”,因为MDD已经开始利用自动化工具来提高软件开发生产率了,但是MDD技术还刚刚处于探索阶段,还没有成为一项大规模应用的成熟技术。对应于这种比喻的话,我们可以看到软件开发技术发展是非常缓慢的,相比其他行业(如集成电路)的技术发展,软件开发技术还很落后。我们大部分人还在使用原始的开发手段,等到将来我们把软件开发技术发展到“开汽车”、“坐飞机”的阶段,可能我们就找到了软件工程的“银弹”。那个时候可能我们只需要把需求告诉电脑就行了,它就会自动地做到我们想要的东西。
走路 跑步 骑自行车 开汽车、坐飞机
结构化方法 面向对象方法 模型驱动开发 将来的软件开发技术
我观察到在实际应用中使用什么样的设计方法往往受制于编程技术和环境,采用COBOL(主机应用开发)、C等传统过程语言的开发人员一般都在使用结构化方法进行设计;而采用Java、C++等面向对象语言的开发人员受到对象概念的熏陶比较多,基本上会转向面向对象方法;当然也有例外,也有一些开发人员在用C++语言实现面向过程的设计,因为他们还没有领悟OO所带来的好处。如果你想从结构化方法转向OOAD方法的话,我建议你先从OOP(Object-Oriented Programming)开始,使用Java、C++这些经典的OO编程语言可以让你逐步掌握OO技术的妙处,这样再转向OO设计就会容易一些,这是自底向上的实践。
********************************************************************************
面向对象和面向过程区别的很好地范例
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。
再比如我要把这个五子棋游戏改为围棋游戏,如果你是面向过程设计,那么五子棋的规则就分布在了你的程序的每一个角落,要改动还不如重写。但是如果你当初就是面向对象的设计,那么你只用改动规则对象就可以了,五子棋和围棋的区别不就是规则吗?(当然棋盘大小好像也不一样,但是你会觉得这是一个难题吗?直接在棋盘对象中进行一番小改动就可以了。)而下棋的大致步骤从面向对象的角度来看没有任何变化。
当然,要达到改动只是局部的需要设计的人有足够的经验,使用对象不能保证你的程序就是面向对象,初学者或者很蹩脚的程序员很可能以面向对象之虚而行面向过程之实,这样设计出来的所谓面向对象的程序很难有良好的可移植性和可扩展性。
*********************************************************************************************
面向对象跟结构化的区别与分析
按我的理解,所谓的面向对象就是就是在程序设计中按类来对系统进行设计。举个例子,要发广告邮件,广告邮件列表存在数据库里面。倘若用C来写的话,一般会这样思考,先把邮件内容读入,然后连接数据库,循环取邮件地址,调用本机的qmail的sendmail命令发送。
然后考虑用Java来实现,既然是OOP,就不能什么代码都塞到main过程里面,于是就设计了三个类:
一个类是负责读取数据库,取邮件地址,调用qmail的sendmail命令发送;
一个类是读邮件内容,MIME编码成HTML格式的,再加上邮件头;
一个主类负责从命令读参数,处理命令行参数,调用发email的类。
仔细的分析一下,就会发现这样的设计完全是从程序员实现程序功能的角度来设计的,或者说,设计类的时候,是自低向上的,从机器的角度到现实世界的角度来分析问题的。因此在设计的时候,就已经把程序编程实现的细节都考虑进去了,企图从底层实现程序这样的出发点来达到满足现实世界的软件需求的目标。类与类之间是通过发送和接收消息相联系的,接收消息的对象通过调用类的方法来实现相应的操作。访问限制符Private、protected和public将类分成三个部分:私有部分、保护部分和公有部分。使数据具有不同的隐蔽程度。类定义可包含一组构造函数和析构函数,构造函数保证了在声明类的对象时对其自动初始化,而析构函数则保证对类的对象正常地清除。从已有的类还可以派生瓣的类,前者称为基类,后者称为派生类,OO方法中继承的原则在这里得以体现。能够突破类的私有部分,禁止其它函数直接访问限制的友员机制,以及由运算符重载、函数名重载和虚函数构成的多形性,使程序员能以更自然、方便的表达方式实现对象的操作。
当然万事万物皆为对象,而对象又是类的实例,因此在面向对象的编程中,就如上面的例子,我们可以在分析三个大类的前提下,定义这些类的对象。大家都知道任何对象都会有
属性,方法,过程,函数,事件等,且类与类之间又可以继承,子类继承父类,子类间有多态性。类也可以先进行封装,再使用。这些都是面向的特性,当然它还有一个代码可重复使用的特性。也是因为如此,在程序设计中如果采用面向对象的编程方式可以更好的以人类的正常思维进行分析。至于具体的分析,到后面将用俄罗斯方块来分析。相比之下,传统的结构化程序设计则采用另外一种编程方式,它是采用按模块功能进行系统分析的,下面附出结构化方法的大概流程图。图略
结构化程序设计的主要思想是功能分解并逐步求精,当一些任务十分复杂以至无法描述时,可以将它拆分为一系列较小的功能部件,直到这些自完备的子任务小到易于理解的程度。例如,计算一个公司中每一个职员的平均工资是一项较为复杂的任务。可以将其拆分为以下的子任务
(1) 找出一个人的收入
(2) 计算总共有多少职员
(3) 计算工资总额
(4) 用职员人数去除工资总额
而计算工资总额本身又可以分为一系列子任务:
(1) 找出每个职员的档案
(2) 读出工资数额
(3) 把工资加到部分和上
(4) 读出下个职员的档案
类似地,读出每个职员档案的记录又可以分解为一系列子任务
(1) 打开职员的档案
(2) 找出正确记录
(3) 从磁盘读取数据
具体看来结构化程序具有以下几个特征:自顶向下,逐步细化,模块化设计,结构化编码
从软件工程发展的历史来看,早期的软件开发量小,结构化程序设计在成功地为处理复杂问题提供了有力的手段。然而到80年代末,它的一些缺点越来越大。比如当数据量增大时,数据与处理这些数据的方法之间的分离使程序变得越来越难以理解。对数据处理能力的需求越强,这种分离所造成的负面影响越显著。并且对于每一种老问题的新方法都要带来额外的开销,与可重用性相比,这种编程思想显得落后许多。面向对象就是在这种情况产生了,相信在未来十年内,面向对象还将继续发展。
介绍完了两者大致的定义后,接下来用俄罗斯方块来分析一下两种编程思想具体的不同
对于结构化程序的方法,在这里用脚本语言javascript来描述,而面向对象则采用如日中天的java来分析。
首先按结构化编程方式来完成俄罗斯方块的话,我们会对问题按数据结构进行模块化的分解,本程序将用以下几个模块来完成:
(1) 开始游戏(function beginGame())
(2) 俄罗斯方向键的控制(function keyControl())
(3) 块的消除(function decline())
(4) 面板的移动(function moveBar())
(5) 暂停游戏(function pauseGame())
(6) 游戏重新开始(function replayGame())
(7) 方块的显示(function randBar())
在分解这几个模块后,就已经大致完成了对程序的整体分析了。这是对程序中问题的描述,接下来则是具体的算法分析,也是结构化当中的解决问题了。要解决本程序中最主要的一个算法一一方块的显示算法,在本程序中采用四维数组的方式来表达方块,比如:
1,0,0,0
1,0,0,0
1,0,0,0
1,1,1,1
这样则表示方块中的L形状态,其他则不在一一列出。其他算法的则略过。还是把重点转移到面向对象中来。
在面向对象中首要解决的是本程序中要分为几大类来完成最基本的系统设计。这个俄罗斯方块放在网页中,也就是采用applet小程序的形式,但由于java跨平台性,它可以在windows,linux,unix等操作系统中使用。本程序大致分为三个大类:Tetrisapplet,Brick3D,Board。下面是TerisApplet的UML图:
TerisApplet Init() Initcomponent() Minewgameactionperformed() Miendgameactionperformed()
|
(1) init() :初始化 applet
(2) initcomponent():初始化窗口以及窗口中的组件
(3) minewGameActionPerformed():当选择菜单或者工具栏上的“开始游戏”命令后,开始新游戏。
(4) miEndGameActionPerformed():当选择或者工具栏上上的“结束游戏”命令后,结束游戏。
2.Brick3D
Brick3D定义主要有reset(),getShapeIno(),getAngle(),getShape(),rotate()和paint()方法。其中getshapeInfo(),getAngle(),getShape()用于获取砖块的形状信息,矩阵、角度和形状值。Totate()方法用于旋转砖块,paint()方法用于在指定的地点给制砖块
3.Board
Board类实现俄罗斯方块的主要大部分功能,下面是它的UML图
略
从建模图中可以看出:Board实现的方法有drop()、move()、reset()、newGame()、endGame()、run()、paint()、createNewBrick()、checkRow()、processKeyEvent()、processMouseEvent()、processMouseWheelEvent()等方法,这些方法的功能定义如下。
(1) drop():砖块的下落函数。
(2) move():移动砖块,1表示向右移动,-1表示向左移动
(3) reset():初始化board_info数组
(4) newGame():开始执行线程(游戏)
(5) endgame():停止执行线程(游戏)
(6) run():线程运行的函数
(7) paint():绘制背景,边界和砖块
(8) createNewBrick():创建下一个新的砖块形状、角度和颜色,角度、形状和颜色随机产生
(9) checkrow():检查是否可以消行,并计算分数
(10) processKeyEvent():处理键盘事件,控制砖块的移动和旋转
(11) processMouseEvent():处理鼠标事件,控制旋转和左右移动
(12) processMouseWheelEvent():处理鼠标滚轮事件,当向下滚动时,控制块向下移动
分析完后,本程序用了三个主要的类:Brick3D对象、Board对象和TerisApplet对象,分别对应Brick3D.java,Board.java和TetrisApplet.java三个文件,在实例中还包括了一个Html文件。
虽然用面向对象写的代码更多,看似面向对象更为麻烦,但实际上面向对象更能解决现在的软件工程问题,而结构化程序设计的方法只对小型的软件设计适合。未来十年内面向对象还是软件开发的主流。