结构化与面向对象(下)
2 面向对象方法
儿子从二年级开始就一直在学Python,这几天问跟我说想用Python编写类似“我的世界”的游戏,而且还兴致勃勃的说,它准备设计一个配置文件,游戏开始时,程序需要根据配置文件绘制游戏场景,场景中会包含基岩、草方块等。我们就以这个“从配置文件读取配置并绘制场景”的问题出发,来看看如何从结构化过渡到面向对象。
儿子设计的配置文件(假设文件名为:scene.cfg)的内容大概是下面这个样子:
1000,511
10,10,基岩100,200,草方块
......
其中第一行为场景的宽和高,从第2行开始,每行代表1个目标,格式为“x坐标,y坐标,目标名称”。
我们先来看看结构化的方法,也就是单纯采用函数的方式如何实现这样一个功能,下面给出粗略的代码:
#全局变量:场景宽度
width = 0
#全局变量:场景高度
height = 0
#全局变量:游戏目标列表,列表中每个游戏目标用一个三元组表示,即(x坐标,y坐标,目标名称)。
lstObject = []
###函数:加载游戏场景
###输入:filename-配置文件名
###输出:True-读取成功,否则读取失败
def loadScene(filename):
#判断配置文件是否存在
if os.path.exists(filename):
try:
#打开配置文件
with open(filename, encoding='UTF-8') as f:
#初始化计数器i,用于记录读取到的信息行数
i = 0
#循环读取文件
while True:
#从文件中读取一行数据到变量s
s = f.readline()
#如果读取到了有效的信息则进行相关处理
if s != None and s != '':
#以英文逗号分隔读取到的信息,并记录到变量l(列表)
l = s.split(',')
#如果得到有效的分割信息,则进行相关处理
if l != None:
#如果i=0,则读取到的是场景大小信息
if i == 0:
#如果信息有效,则把读取到的数据分别计入width和height
if len(l) > 1:
width = int(l[0])
height = int(l[1])
#否则,读取到的是游戏目标信息
else:
#如果信息有效,则把读取到的数据存入lstObject
if len(l) > 2:
lstObject.append((int(l[0]), int(l[1]), l[2]))
#否则,终止读取
else:
break
#更新计数器
i = i + 1
return True
except:
pass
return False
#调用函数,读取游戏配置
if loadScene('scene.cfg'):
print('读取成功')
else:
print('读取失败')
那么,如果采用面向对象的方法,代码该是什么样子呢?其实,我们可以将读取配置文件以及文件中包含的游戏目标对象化,也就是把它们封装成类。下面给出大致的代码:
#导入游戏配置模块
import conf
#全局变量:场景宽度
width= 0
#全局变量:场景高度
height=0
#实例化一个配置文件对象并获取配置信息
config = conf.SceneConfig('scene.cfg')
if config.errorNo = 0:
width = config.getWidth()
height = config.getHeight()
print('读取成功')
else:
print('读取失败,错误号:', errorNo)
比较一下,有什么变化?首先,代码明显变短啦;其次,文件头部增加了一行“import ...”;最后,一个最明显的变化就是用到了一个名为config的对象,用于专门处理配置文件的读取。下面,我们来看看这个config对象是怎么回事儿?其实,就是在头部应用的conf模块中,我们将读取配置文件这件事儿,封装成一个名为SceneConfig的类。具体的代码如下:
#conf.py
#场景目标类
class GameObject:
x = 0
y = 0
text = None
#构造函数
def __init__(self, x, y, text):
self.x = x
self.y = y
self.text = text
#构造函数
def __init__(self):
self.x = 0
self.y = 0
self.text = None
#场景配置操作类
class SceneConfig:
#场景宽度
width = 0
#场景高度
height = 0
#场景包含的目标列表
lstObject = []
#操作过程中的错误信息
error = ''
#操作过程中的错误号
errorNo = 0
#构造函数
#参数:filename-配置文件名
def __init__(self, filename):
#判断配置文件是否存在
if os.path.exists(filename):
try:
#打开配置文件
with open(filename, encoding='UTF-8') as f:
#初始化计数器i,用于记录读取到的信息行数
i = 0
#循环读取文件
while True:
#从文件中读取一行数据到变量s
s = f.readline()
#如果读取到了有效的信息则进行相关处理
if s != None and s != '':
#以英文逗号分隔读取到的信息,并记录到变量l(列表)
l = s.split(',')
#如果得到有效的分割信息,则进行相关处理
if l != None:
#如果i=0,则读取到的是场景大小信息
if i == 0:
#如果信息有效,则把读取到的数据分别计入width和height
if len(l) > 1:
self.width = int(l[0])
self.height = int(l[1])
#否则,读取到的是游戏目标信息
else:
#如果信息有效,则把读取到的数据存入lstObject
if len(l) > 2:
lstObject.append((int(l[0]), int(l[1]), l[2]))
#否则,终止读取
else:
break
#更新计数器
i = i + 1
except Exception as e:
self.error = e.strerror
self.errorNo = e.errorno
#获取第i个场景目标
def getObject(self, i):
if i >= 0 and i < len(self.lstObject):
return self.lstObject[i]
return None
#获取场景目标的个数
def getCount(self):
return len(self.lstGameObject)
#获取场景宽度
def getWidth(self):
return self.width
#获取场景高度
def getHeight(self):
return self.height
那么,这样做有什么好处呢?接下来让我们一起来探讨一下。
2.1 面向对象方法的发展历程
面向对象方法是将面向对象的思想应用于软件开发过程中,用于指导开发活动,是建立在“对象”概念基础上的方法学,简称OO(Object-Oriented)方法。
OO方法起源于面向对象的编程语言(简称为OOPL)。50年代后期,在用FORTRAN语言编写大型程序时,常出现变量名在程序不同部分发生冲突的问题。鉴于此,ALGOL语言的设计者在ALGOL60中采用了以"Begin……End"为标识的程序块,使块内变量名是局部的,以避免它们与程序中块外的同名变量相冲突。这是编程语言中首次提供封装(保护)的尝试。此后程序块结构广泛用于Pascal 、Ada、C等高级语言之中。
60年代中后期,Simula语言在ALGOL基础上研制开发,它将ALGOL的块结构概念向前发展一步,提出了对象的概念,并使用了类,也支持类继承。
70年代,Smalltalk语言诞生,它将Simula提出的类作为核心概念,其很多内容也借鉴于Lisp语言。Xerox公司经过对Smalltalk72、76持续不断的研究和改进之后,于1980年推出商品化的Smalltalk,它在系统设计中强调对象概念的统一,引入对象、类、方法、实例等概念和术语,采用动态联编和单继承机制。
从80年代起,人们基于以往提出的有关信息隐蔽和抽象数据类型等概念,以及由Modula2、Ada和Smalltalk等语言所奠定的基础,再加上客观需求的推动,进行了大量的理论研究和实践探索,不同类型的面向对象语言(如:Object-c、Eiffel、c++、Java、Object-Pascal等)如雨后春笋般的发展起来,出现了OO方法的概念理论体系和实用的软件系统。
80年代以来,面向对象方法已被广泛应用于程序设计语言、形式定义、设计方法学、操作系统、分布式系统、人工智能、实时系统、数据库、人机接口、计算机体系结构以及并发工程、综合集成工程等,在许多领域的应用都得到了很大的发展。
1986年在美国举行了首届“面向对象编程、系统、语言和应用(OOPSLA'86)”国际会议,使面向对象受到世人瞩目,其后每年都举行一次,这进一步标志OO方法的研究已普及到全世界。
面向对象方法可以追溯到20世纪60年代末和70年代初,当时计算机科学家开始意识到传统的过程式编程方法在大型软件开发中存在一些问题,如传统的过程式编程方法往往将数据和操作分开处理,导致了代码的复杂性和可维护性的下降。
在这个背景下,面向对象方法应运而生。面向对象方法将数据和操作封装在一个对象中,通过定义对象之间的关系和交互,实现了更加模块化、可复用和易于维护的软件开发方式。
面向对象方法的发展可以分为以下几个阶段:
类和对象的概念的提出:20世纪60年代末和70年代初,Simula语言的出现提出了类和对象的概念,这是面向对象方法的基础。
Smalltalk的出现:20世纪70年代末和80年代初,Smalltalk语言的出现推动了面向对象方法的发展。Smalltalk是第一种完全基于面向对象思想的编程语言,它提供了丰富的面向对象特性,如封装、继承和多态。
类库和框架的发展:20世纪80年代和90年代,面向对象方法开始广泛应用于软件开发中。各种编程语言和开发工具提供了大量的类库和框架,使得开发人员可以更加方便地使用面向对象方法进行软件开发。
设计模式的提出:20世纪90年代,设计模式的概念被提出,成为了面向对象方法的重要组成部分。设计模式是一套被证明有效的软件设计解决方案,提供了一种将通用设计问题抽象化的方法。
软件工程的发展:21世纪以来,面向对象方法在软件工程中得到了广泛应用,成为了一种主流的软件开发方法。同时,面向对象方法也在不断发展和完善,例如引入了面向方面编程(Aspect-Oriented Programming)和领域驱动设计(Domain-Driven Design)等新的概念和技术。
从面向对象语言的出现,再到类库和框架的发展和设计模式的提出,最终形成了一套完整的面向对象开发方法和工具体系。面向对象方法的发展不仅推动了软件开发的进步,也为软件工程提供了更加科学和可行的解决方案。
2.2 面向对象的思想和主要概念
在结构化方法中,软件需求分析是基于数据流图、E-R图、状态转换图等加以表示,软件设计是基于模块、函数、过程等一组概念和抽象,显然,需求分析和软件设计分别处于不同的概念空间,因而需要通过转换的方式,将数据流、E-R图、状态转换图等描述的需求模型通过映射(如数据流图的分解、变换等),生成用模块、过程等概念描述的软件设计模型。
面向对象的方法则不同,软件设计和需求分析都是基于相同的抽象和概念,即都是基于诸如对象、类、消息传递、继承等概念来表示,因而,采用面向对象方法,从需求分析模型到软件设计模型无需进行转换,而是通过不断的精华和优化,将支持软件实现的更多设计元素引入分析模型之中,实现从需求到设计的自然过渡,减少两个阶段的概念鸿沟,进而简化软件设计阶段的工作。
面向对象的主要概念包括对象、类、封装、继承和多态等。
(1)对象(Object)。由数据及其操作所构成的封装体,是系统中用来描述客观事物的一个模块,是构成系统的基本单位。
- 对象可以是物理实体的抽象,比如一名职工、一辆汽车、一家公司;也可以是人为的概念,比如一项计划或一个抽象的概念。
- 在面向对象的系统模型中,对象是构成系统的一个基本单位,它由一组属性和对这组属性进行操作的一组操作构成。属性是用来描述对象静态特征的一个数据项。操作是用来描述对象行为的一个动作序列。
- 对象、对象的属性和对象的操作都有各自的名字。
(2)类(Class)。类是具有相同属性和操作的一组对象的集合。
- 类的作用是创建对象,对象是类的一个实例。例如,在教学档案管理系统中,“教师”是一个类。“教师”类具有“姓名”、“性别”、“工号”等属性,还具有“上传档案”和“查询档案”等操作。一个具体的教师就是“教师”这个类的一个实例。同一个类所产生的对象之间一般有着不同点,因为每个对象的属性值可能是不同的。
- 在设计类时,要用到两个概念:抽象与分类。抽象就是忽略事物的非本质特征,只注意与当前目标有关的本质特征,从而找出事物的共性。把具有共同性质的事物划分为一类,得出一个抽象的概念,这种做法叫作分类。
- 在开发系统时,先要根据完整的需求描述,对现实世界中的对象进行分析与归纳,以此为基础来定义系统中的对象并将其抽象为类:其中一部分类是对现实世界中的物理实体的抽象,这些类只包含与所解决的现实问题有关的那些内容;另一部分类则是为了构建系统而人为设立的。例如在设计“教学档案管理系统”时,教师、课程、档案等都是现实世界中存在的真实对象,我们将其抽象为名为Teacher、Course、CourseFile等类;而用于管理这些对象信息的界面类、控制类、数据库访问类,则是为了便于构建系统而人为设立的,如TeacherManageUI(教师管理界面类)、TMController(教师管理控制类)、DBUtils(数据库访问类)等。
(3)封装(Encapsulation)。封装是一种将数据和方法封装在对象内部,对外部隐藏实现细节的机制。通过封装,我们可以将对象的属性和行为结合在一起,形成一个单独的、独立的个体,外部只能通过对象的接口来访问和操作对象。封装有以下优点:
- 提高了代码的可复用性和可维护性:对象的实现细节被封装在对象内部,外部不知道对象内部是如何实现的,只需要通过对象的接口来使用对象,这样在对象内部进行修改时,不会影响到外部的代码。
- 隐藏了实现细节:外部无法直接访问对象的属性和方法,只能通过接口进行访问,这样可以保护对象的数据和方法,防止被误用或恶意篡改。
- 提高了安全性:对象的属性和方法只能通过接口进行访问和修改,可以对访问和修改进行控制,从而提高了代码的安全性和可靠性。
(4)继承(Inheritance)。继承是指特殊类自动地拥有或隐含地复制其一般类的全部属性与操作,这种机制也称作一般类对特殊类的泛化(Generalization)。例如,在教学档案管理系统中,系主任就是特殊的教师,类“系主任”作为特殊类继承了一般类“教师”的所有属性和操作。
- 继承性是面向对象程序设计语言不同于其它语言的最重要的特点,是其他语言所没有的。
- 在类的继承层次结构中,位于较高抽象层次的类叫作一般类,而位于较低抽象层次的类叫作特殊类。
- 有时一个类要同时继承两个或两个以上一般类中的属性和操作,把这种允许一个特殊类具有一个以上一般类的继承模式称为多重继承。
- 继承简化了人们对事物的认识和描述,简化了系统的构造过程及其文档,有益于软件复用,这也是面向对象方法能提高软件开发效率的重要原因之一。
- 在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
- 在软件开发中,类的继承性使所建立的软件具有开放性、可扩充性,这是信息组织与分类的行之有效的方法,它简化了对象、类的创建工作量,增加了代码的可重用性。
(5)多态(Polymorphic)。多态是指同一种操作可以作用于不同的类,并可能以多种形式呈现。
多态有以下实现方式:
- 重写(override):子类可以重写父类的方法,从而改变方法的具体实现。当父类的引用指向子类的实例时,调用的方法就是子类重写的方法。
- 重载(overload):一个类中可以有多个名称相同但参数列表不同的方法,称为方法的重载。当调用这些方法时,编译器会根据方法的参数类型和数量来区分调用哪一个方法。有两种重载:函数重载和运算重载。前者是指在同一作用域内的若干个参数特征不同的函数可以使用系统的函数名字;后者是指同一个运算符可以施加于不同类型的操作数上面。
多态有以下优点:
- 提高了代码的灵活性和可扩展性:通过多态,可以将同一种操作应用于不同的对象,从而使得代码可以应对不同的情况和需求。
- 简化了代码:通过多态,可以对一类相似的对象进行统一的处理,从而简化了代码的编写和维护。
2.3 面向对象方法的主要优点
与传统的结构化方法相比,面向对象方法的主要优点包括:
(1)面向对象方法与人类习惯的思维方法一致。
- 面向对象方法从对象出发认识问题域,对象对应着问题域中的事物,其属性与操作分别刻画了事物的性质和行为,完整、自然地表示客观世界中的实体。
- 对象之间的继承、聚合、关联和依赖关系如实地表达了问题域中事物之间实际存在的各种关系。
(2)面向对象方法使分析与设计之间无缝连接。
- 在结构化方法中,对问题域的描述与认识并不以问题域中的固有事物作为基本单位,而是在全局的范围内以功能、数据或数据流为中心来进行分析,所以运用该方法得到的分析结果不能直接地映射到问题域。这样就容易造成一些对问题域理解的偏差。此外,由于分析与设计的表示体系不一致,导致了设计文档与分析文档很难对应,因而很容易因理解上的错误而埋下隐患。正是由于这些隐患,使得编程人员经常需要对分析文档和设计文档进行重新认识,以产生自己的理解再进行工作,使得分析文档、设计文档和程序代码之间不能较好地衔接,这一特性也给维护阶段对所发现问题的回溯带来很多理解上的障碍。
- 面向对象方法的各个阶段都使用了一致的概念与表示法,而且这些概念与问题域的事物是一致的,这对整个软件生命周期的各种开发和管理活动都具有重要的意义。首先是分析与设计之间不存在鸿沟,从而可减少人员的理解错误并避免文档衔接不好的问题。从设计到实现,模型与程序的主要成分是严格对应的,这不仅有利于设计与实现的衔接,而且还可以利用工具自动生成程序的框架和部分代码。对于测试而言,面向对象的测试工具不但可以依据类、继承和封装等概念与原则提高程序测试的效率与质量,而且可以发现程序与分析和设计模型之间的不一致。这一特性也有助于软件维护人员更好的理解软件并提高软件维护效率。
(3)面向对象方法有助于软件的复用与维护。
- 在结构化方法中,所有的软件都以功能(可用过程或函数实现)作为其主要构造块,数据结构与算法是分别组织的,对一处进行修改,可能会引起连锁反应。这种建模的缺点是模型脆弱,难以适应不可避免的错误修改以及需求变动,以至于系统维护困难。算法和数据的分离是造成这种状况的根本原因。算法和数据间的紧密耦合,使得软件复用难以实现。
- 在面向对象方法中,把数据和对数据的处理作为一个整体,即对象。该方法以众多的对象及交互模式为中心,把易变的数据结构和部分算法封装在对象内并加以隐藏,仅供对象自己使用,这保证了对它们的修改并不会影响其他的对象。这样对需求变化有较强的适应性,有利于维护。把对象的属性和操作封装在一起,提高了对象的内聚性,减少了与其他对象的耦合,这为复用对象提供了可能性和方便性。在继承结构中,特殊类对一般类的继承,本身就是对一般类的属性和操作的复用。
(4)面向对象方法有助于开发大型高质量软件。
- 面向对象的软件稳定性较好。当对软件的功能或性能的要求发生变化时,通常不会引起软件的整体变化,往往只需对局部做一些修改,比较容易实现。
- 面向对象的软件技术符合人们习惯的思维方式,用这种方法所建立的软件系统的结构与问题空间的结构基本一致,所以软件系统比较容易理解,从而降低修改和扩充系统的工作量。对类的测试通常比较容易实现,如果发生错误也往往集中在类的内部,比较容易调试。
- 采用面向对象方法开发软件时,构成软件系统的每个对象就像一个微型程序,有自己的数据、操作、功能和用途。因此,把一个大型软件产品分解成一系列本质上相互独立的小产品来处理,不仅降低了开发难度,而且使得开发管理工作变得轻松容易。许多软件开发公司的经验表明,当采用面向对象方法开发大型软件系统时,软件成本明显降低,软件整体质量也提高了。
2.4 面向对象软件工程过程
将面向对象方法应用于软件工程各个过程,便是面向对象软件工程过程。包括:
(1)面向对象分析(Object-Oriented Analyse,简称OOA )。OOA 的基本任务是:运用面向对象的概念对问题域进行分析和理解,对其中的事物和它们之间的关系产生正确的认识,关键是识别出问题域内的类和对象,定义这些类和对象的属性与操作,并分析它们相互间的关系。最终建立起问题域的简洁、精确、可理解的正确模型。具体过程包括:
- 建立对象模型。对象模型描述了系统中的静态结构,是面向对象分析方法中的核心模型。对象模型通过定义类和类的关系,如继承、关联、聚合等,来构建系统的静态结构。这种结构化的描述方式有助于开发者清晰地理解系统的组成和各个部分之间的关系,为后续的设计和实现提供有力支持。面向对象分析的首要任务是建立问题域的对象模型。类与对象是在问题域中客观存在的,系统分析员的主要任务就是通过分析找出这些类与对象。首先找出所有候选的类与对象,然后从候选的类与对象中筛掉不正确的或不必要的。接下来分析确定类与对象之间存在的关联关系。分析确定关联,能促使分析员考虑问题域的边缘情况,有助于发现那些尚未被发现的类与对象。经分析得到的关联只能作为候选的关联,还需要进一步筛选,以去掉不正确的或不必要的关联。之后,应该进一步完善经筛选后余下的关联。
- 建立动态模型。动态模型描述了系统中的动态行为,即对象之间的交互和状态的变化。在动态模型中,对象被看作是具有生命周期和状态变化的实体,而交互则被看作是对象之间通过消息传递进行的通信。建立动态模型的一般步骤包括:1)编写典型交互行为的脚本,即编写系统在某一执行期间内出现的一系列交互事件。可以包括系统中发生的全部事件,也可以只包括由某些特定对象触发的事件。大多数交互行为可以分为应用逻辑和用户界面两部分。应用逻辑是内在的、本质的内容;用户界面是外在的表现形式。软件开发人员往往快速地建立起用户界面的原型,以供用户试用与评价。2)从脚本中提取出事件,确定触发每个事件的动作对象以及接受事件的目标对象。3)排列事件发生的次序,确定每个对象可能有的状态及状态间的转换关系。面向对象方法通常通过顺序图、状态图、活动图等图形化工具来描述系统的动态行为。
- 建立功能模型。功能模型描述了系统的功能需求,即系统应该完成哪些任务或提供哪些服务。在功能模型中,系统的功能被分解为一组相互关联的功能模块,每个功能模块都负责完成特定的任务或提供特定的服务。面向对象方法通常通过用例图等图形化工具来展示系统的功能结构。通过功能模型,开发者可以更加准确地理解系统的功能需求,从而确保软件系统的正确性和完整性。
- 定义服务。对象是由描述其属性的数据及可以对这些数据施加的操作(即服务)封装在一起构成的独立单元。为建立完整的对象模型,既要确定类中应该定义的属性,又要确定类中应该定义的服务。通常在完整定义每个类中的服务之前,需要先建立起动态模型和功能模型,通过对这两种模型的研究,能够更正确更合理地确定每个类应该提供哪些服务。
(2)面向对象设计(Object-Oriented Design,简称OOD)。分析是提取和整理用户需求并建立问题域精确模型的过程。设计则是把分析得到的需求转变为符合成本和质量要求的、抽象的系统实现方案。或者说,OOD是在OOA的基础上运用面向对象方法建立求解域模型的过程。从OOA到OOD是一个逐渐扩充模型和多次反复迭代的过程。具体过程包括:
- 设计问题域子系统。通过面向对象分析得到的问题域模型,为设计问题域子系统奠定了良好的基础,建立了完整的框架。通常,面向对象设计仅需从实现角度对问题域模型做一些补充或修改,主要是增添、合并或分解类与对象、属性及服务,调整继承关系等。当问题域子系统过于复杂时,应该把它进一步分解成若干个更小的子系统。
- 设计人机交互子系统。在面向对象分析过程中,已经对用户界面需求做了初步分析,在面向对象设计过程中,则应该对系统的人机交互子系统进行详细设计,以确定人机交互的细节,其中包括指定窗口和报表形式、设计命令层次等内容。由于对人机界面的评价在很大程度上由人的主观意识决定,因此,使用由原型支持的系统化的设计策略,是成功地设计人机交互子系统的关键。
- 设计任务管理子系统。从概念上说,不同对象可以并发地工作,但在实际系统中,许多对象之间往往存在相互依赖的关系。因此,设计工作的重要任务是,确定哪些是必须同时动作的对象,哪些是相互排斥的对象,然后设计任务管理子系统,即:确定任务类型并把任务分配给适当的硬件或软件去执行。常见的任务类型有事件驱动型任务、时钟驱动型任务、优先任务、关键任务和协调任务等。
- 设计数据管理子系统。数据管理子系统是系统存储或检索对象的基本设施,它建立在某种数据存储管理系统之上,并且隔离了数据存储管理模式的影响。设计者应该根据应用系统的特点选择适用的数据存储管理模式并设计相应的数据格式和服务。数据存储管理模式包括文件管理系统、关系数据库管理系统和面向对象数据库管理系统等。
- 设计类中的服务。面向对象分析得出的对象模型,通常并不详细描述类中的服务。面向对象设计则是扩充、完善和细化面向对象分析模型的过程,设计类中的服务是它的一项重要工作内容。
- 设计关联。在面向对象设计过程中,设计人员必须确定实现关联的具体策略,并与它在应用系统中的使用方式相适应。
- 设计优化。系统的各项质量指标并不是同等重要的,设计人员必须确定各项质量指标的相对重要性(即优先级),以便在优化设计时指定折衷方案。
(3)面向对象程序设计(Object-Oriented Programming,简称OOP)。采用一种面向对象的编程语言,实现满足上述设计要求的软件。
2.5 面向对象软件工程工具
2.5.1 统一建模语言(UML)
传统的结构化方法提供了数据流图、实体关系图、结构图、流程图等各种建模技术来支持结构化分析和设计。同样,为了支持面向对象的分析和设计过程,先后出现了很多相关的建模技术,如Booch方法、OMT(Object ModelingTechnique)方法、OOSE(Object-Orient Software Engineering)方法等。但这些方法最终得到了统一,并形成了统一建模语言(Unified Modeling Language,UML)。
如图所示,UML产生于20世纪80年代末至90年代。它是由信息系统和面向对象领域的三位著名的方法学家Grady Booch、James Rumbaugh和Ivar Jacobson提出的,后在对象管理组织(Object Management Group,OMG)的推动下不断发展,它不仅统一了众多面向对象表示方法,而且在此基础上有了进一步的发展,并最终统一为大众所接受的标准建模语言(已被ISO组织发布为国际标准:ISO/IEC 19501-2005,ISO/IEC 19505-2012)。
图2-23 UML发展历程
https://www.douban.com/note/253524901/?_i=5820775SUTbS2O | 知识链接:Grady Booch、James Rumbaugh和Ivar Jacobson是UML的创始人,均为软件工程界的权威,除了著有多部软件工程方面的著作之外,在对象技术发展上也有诸多杰出贡献,其中包括Booch方法、对象建模技术(OMT)和Objectory(OOSE)过程。三人被合称为“UML三友”。 |
这里只对对UML作简单介绍,在下一章我们会对UML作详细介绍。
2.5.2 面向对象编程语言(OOPL)
如前所述,面向对象方法是从面向对象编程开始的,以下是一些常见的面向对象编程语言(Object-Oriented Programming Language,OOPL):
- Java:Java是一种广泛使用的面向对象编程语言,具有跨平台性、可靠性和安全性等特点。它有强大的类库支持,可以用于开发各种类型的应用程序。
- C++:C++是一种高效、通用的面向对象编程语言,常用于系统级编程和性能要求较高的应用程序。它提供了丰富的库支持,并且可以与C语言进行混合编程。
- C#:C#是微软公司开发的一种面向对象编程语言,主要用于开发Windows应用程序和Web应用程序。它有类似于Java的语法和框架,同时也支持.NET技术。
- Python:Python是一种简单易学的面向对象编程语言,它具有清晰简洁的语法和丰富的第三方库。Python广泛应用于科学计算、Web开发和数据分析等领域。
- Ruby:Ruby是一种动态、开放源代码的面向对象编程语言,它具有简洁的语法和强大的元编程能力。Ruby被广泛应用于Web开发和脚本编程。
除了以上几种语言,还有其他面向对象编程语言,如Objective-C、Swift、JavaScript等。选择适合自己的编程语言,应该考虑项目需求、团队技能、开发效率和生态系统等因素。
2.5.3 集成开发环境(IDE)
从原理上来讲,我们开发程序时可以直接在类似记事本的编辑器中完成代码编写,然后运行对应语言的编译器或解释器将其翻译成可执行的程序,但这样显然不是聪明的做法。为了提高开发效率,针对不同的编程语言,我们可以使用对应的集成开发环境(Integrated Development Environment , ,IDE)来实现软件的快速开发。
IDE的主要特点是提供了丰富的开发工具和一体化的开发环境,使得开发者可以在一个统一的界面中完成代码编写、编译、调试、测试等多种开发任务,从而大大提高了开发效率。此外,IDE通常还具备代码补全、语法高亮、错误提示、版本控制等功能,这些功能可以进一步简化软件开发流程,提高代码质量。下表给出了目前主流的一些IDE:
IDE 名称 | 支持语言 | 平台 | 主要功能 | 优点 | 缺点 | 适用场景 |
Visual Studio | C#, C++, Python, JavaScript, .NET 技术栈 | Windows, macOS(有限支持) | 企业级调试器、代码分析、Azure 集成、Unity 游戏开发支持 | 功能全面,调试能力极强;深度集成微软生态(如 Azure、.NET) | 资源占用大;macOS 版本功能受限;专业版价格昂贵 | 大型企业应用、游戏开发(Unity)、微软技术栈开发(.NET Core) |
IntelliJ IDEA | Java, Kotlin, Groovy, Scala(终极版支持 Python、SQL、JS 等) | 跨平台(Win/macOS/Linux) | 智能代码补全、重构工具、数据库集成、Spring 框架支持 | 对 Java 生态支持最佳;插件丰富(如 Docker、K8s);终极版覆盖全栈开发 | 内存占用较高;终极版需付费订阅;学习曲线较陡 | Java/Kotlin 企业应用、Android 开发、Spring 后端开发 |
VS Code | 多语言(通过插件支持:Python, Java, Go, C++ 等) | 跨平台 | 轻量级编辑器、Git 集成、远程开发、海量插件扩展 | 免费开源;启动速度快;插件生态强大;适合轻量级开发 | 深度语言支持依赖插件;复杂项目调试能力有限 | 前端开发、脚本语言(Python/JS)、轻量级全栈开发 |
PyCharm | Python(专业版支持 JS、SQL、HTML 等) | 跨平台 | 科学计算工具(Jupyter 集成)、Django/Flask 框架支持、数据库工具 | Python 专属优化(如代码分析、调试);专业版支持远程开发和数据科学 | 专业版需付费;资源占用高于 VS Code;对非 Python 语言支持弱 | Python Web 开发、数据科学、机器学习(Jupyter 集成) |
Eclipse | Java, C/C++, PHP, Python(需插件) | 跨平台 | 模块化插件系统、基础调试、Git 集成 | 免费开源;适合教育和小型项目;历史悠久的 Java 生态支持 | 界面老旧;大型项目卡顿;插件配置复杂 | 教学、传统 Java 企业应用、嵌入式开发(C/C++) |
Xcode | Swift, Objective-C, C/C++ | macOS 独占 | iOS/macOS 模拟器、Interface Builder、性能分析工具 | Apple 生态开发唯一选择;深度集成 Swift 调试工具;性能优化强 | 仅限 macOS;对非 Apple 技术栈支持差;项目配置复杂 | iOS/macOS 应用开发、Swift 语言开发 |
Android Studio | Java, Kotlin, C++(NDK) | 跨平台 | 安卓模拟器、布局编辑器、APK 分析工具、Jetpack 组件集成 | 官方安卓开发工具;深度优化 Kotlin;实时布局预览 | 依赖硬件性能(尤其是模拟器);Gradle 构建速度慢 | 安卓应用开发、移动端跨平台工具(如 Flutter) |
从上表可以看出,每个IDE都有其独特的功能和优势。我们在选择IDE时,可从以下几个方面进行考量:
- 开发语言:如果使用Java开发程序,可能我们最好的选择是Eclipse或IntelliJ IDEA,如果是C#,则选择Visual Studio更合适。
- 跨平台性:VS Code、Eclipse、NetBeans、IntelliJ IDEA和PyCharm都是跨平台的IDE,支持Windows、macOS和Linux。而Visual Studio主要适用于Windows平台,Xcode则专为macOS设计。
- 插件生态系统:VS Code以其强大的插件生态系统而著称,支持几乎所有主流开发语言。其他IDE也有插件支持,但可能相对专注于特定的编程语言。
- 适用场景:Visual Studio(C#/.NET)、IntelliJ IDEA(Java)更适合企业级开发;Xcode(iOS)、Android Studio(安卓)适合移动开发;PyCharm 专业版、VS Code + 插件适合数据科学与脚本类开发。
- 价格:许多IDE提供免费版本,如VS Code、Eclipse、NetBeans和IntelliJ IDEA的社区版。而专业版或企业版则可能需要付费,如IntelliJ IDEA的Ultimate版、PyCharm的专业版和Visual Studio的专业版。
3 结构化与面向对象相结合的软件工程过程
3.1 结构化和面向对象方法对比
下表给出了两种方法的对比:
特征/方法 | 结构化软件工程 | 面向对象软件工程 |
---|---|---|
基本单位 | 模块 | 对象 |
基本思想 | 把复杂问题分阶段进行,自顶向下,逐层分解 | 尽可能模拟人类习惯的思维方式,使开发软件的方法与过程尽可能接近人类认识世界、解决问题的方法 |
分析方法 | 面向数据流,基于功能分解,使用数据流程图、数据字典等工具 | 将问题域和系统的认识理解抽象为规范的对象和消息传递联系,建立对象模型、动态模型、功能模型 |
主要特点 | 模块化、逐步求精、自顶向下、控制结构化、数据独立性、文档化、可维护性 | 可重用性、可扩展性、可维护性,通过继承、封装、多态等概念构造软件系统 |
优点 | 理论基础严密,开发过程规范,适合初学者和中小型项目 | 直观、方便,易于理解和维护,适合复杂系统的开发 |
缺点 | 开发周期长,文档和设计说明繁琐,可重用性较差 | 开发过程管理要求高,系统复用性低,缺乏规范化的文档资料,不易于后期维护 |
适用场景 | 数据少而操作多的问题,如操作系统等以功能为主的系统 | 数据库、信息管理等以数据为主的系统 |
从上表可以看出,结构化方法和面向对象方法各有自己的优缺点,主要表现在如下几个方面:
- 从执行效率来说。结构化方法比面向对象方法,产生的可执行代码更直接、更高效。所以对于一些嵌入式的系统,结构化方法产生的系统更小,运行效率更高。
- 从重用性方面来说。采用结构化方法的系统难以修改和扩充。(1)结构化分析与设计清楚定义了系统的接口,当系统对外界接发生变动时,往往造成系统结构较大变动,难以扩充新的功能接口,采用结构化方法的系统可复用性较差。(2)结构化方法将数据和操作分离,导致一些可重用的软件构件在特定具体应用环境才能应用,降低了软件的可重用性。相比之下,面向对象方法具有很好的重用性。在遇到类似的问题,通过应用了抽象、继承等技术来重用代码。
- 从程序语言编译器来说。面向对象方法,通过编译器实现代码的面向对象性。也就是说经过编译器后,代码会被翻译为相对应的结构化代码。所以要熟练开发,还要懂一定的结构化方法做为基础。
- 从掌握难度来说。面向对象方法比结构化对象方法复杂难于理解。面象对象方法的内容广、概念多,而且很多都是难于理解,做到精通更加不易。应用面向对象方法,常常需要一种支持面向对象的分析、设计方法,如RUP方法、敏捷方法等。这些知识抽象枯燥难于掌握。面象对象方法要经过长期的开发实践才能很好的理解和掌握。相比之下,结构化方法知识内容少、容易上手。
- 从应用的范围看。结构化方法适用于数据少而操作多的问题。实践证明对于类似操作系统这样的以功能为主的系统,结构化方法比较适应它;面向对象方法正好相反,对于数据库、信息管理等以数据为主的而操作较少的系统,用面向对象方法描述要好于结构化方法。
3.2 把结构化和面向对象结合起来开发软件
如前所述,面向过程的结构化方法更接近于计算机世界的物理实现;而面向对象更符合人类的认识习惯。相对传统的结构化方法来说,面向对象的方法具有更多的优势,但任何方法都不是十全十美的,结构化方法经过了50多年的发展和积累,已经有了一整套的工具和成熟的方法学,在发展其他软件方法的同时绝对不能全盘抛弃它。在软件开发过程中,应将二者结合,采用一种综合的软件开发过程,即在分析和设计阶段主要采用结构化(SA)分析和结构化设计(SD),生成软件的结构图,然后在模块设计时应用面向对象设计方法(OOD),如用例图、类图等完成系统功能模块的建模过程,最后使用面向对象编码完成程序的设计。使用SA、SD节省分析的时间、减少错误,使用OOD又使程序设计交互性强、适应性好。
参考资料:
[1] 黄锡伟,胡建彰.一种基于面向对象思想和结构化技术的软件开发方法[J].小型微型计算机系统,1996,17,(4):17-24.
[2] 刘璘,周明辉,尹刚.大数据时代软件工程专题前言[J].软件学报,2017,28,(6):1327-1329.DOI:10.13328/j.cnki.jos.005233.
[3] 卫宏春.三种主流软件工程方法的比较[J].微电子学与计算机,2002,19,(3):5-7.DOI:10.3969/j.issn.1000-7180.2002.03.002.
[4] 单家凌.结构化方法与面向对象可结合性研究[J].电脑知识与技术,2007,2,(2):1057-1057,1101.DOI:10.3969/j.issn.1009-3044.2007.04.089.
[5] 曹建福,周理琴.基于构件的软件开发模型及其实现[J].小型微型计算机系统,2002,23,(6):739-742.DOI:10.3969/j.issn.1000-1220.2002.06.025.
[6] 缪淮扣,高晓雷,李刚.结构化方法、面向对象方法和形式方法的比较与结合[J].计算机工程与科学,1999,21,(4):27-31.
[7] 王小明,冯德民.信息系统需求分析的面向对象层次分析方法及应用[J].计算机工程与应用,2001,37,(3):67-68.DOI:10.3321/j.issn:1002-8331.2001.03.022.
[8] 谢嘉成,蒋建民,陈华豪等.UML类图的一致性分析[J].软件导刊,2024,23,(2):40-47.DOI:10.11907/rjdk.231964.
[9] 马晓星,刘譞哲,谢冰等.软件开发方法发展回顾与展望[J].软件学报,2019,30,(1):3-21.DOI:10.13328/j.cnki.jos.005650.
[10] 汪烨,陈骏武,夏鑫等.智能需求获取与建模研究综述[J].计算机研究与发展,2021,58,(4):683-705.DOI:10.7544/issn1000G1239.2021.20200740.
[11] https://zhuanlan.zhihu.com/p/690665150
[12] UML术语标准与分类详解-CSDN博客
[13] 面向对象分析与设计(Grady Booch 等著) - void87 - 博客园
[14] 面向数据结构的设计方法(Jackson方法)_马赛马拉--角马_新浪博客