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年代以来,面向对象方法已被广泛应用于程序设计语言、形式定义、设计方法学、操作系统、分布式系统、人工