面向对象理论很早就被提出了,但它真正地渗透到软件开发的各个领域,并且在软件开发实践中大规模应用,却要等到上世纪九十年代。到目前为止,面向对象技术已是软件开发的主流,全面取代了结构化编程技术曾经具有的地位。
面向对象技术与结构化编程技术有着不同的风格,但同时也有着密切的联系。从具体编程角度来看,面向对象技术与结构化编程技术很难截然分开,两者的根本差别在于思维
方式。
要了解面向对象技术,得从结构化编程技术入手。
1.1结构化编程引例
结构化编程在上世纪七十年代兴起,由于它具有很多的优点,出现之后没几年就占据了软件开发的主流,大家熟悉的 C语言就是一种典型的结构化编程语言。
结构化编程的基本方法是“功能分解法”,具体来说,就是将要解决的实际问题进行分解,把一个大问题分成若干个子问题,每个子问题又可以被分解为更小的问题,直到得到的子问题可以用一个函数来实现为止。
我们先从一个简单的编程任务开始,逐步体会结构化编程与面向对象编程的不同风格。请看以下的这道编程题目:
请编程计算出1999年5月10日到2006年3月8日期间一共有多少天?
这是一个简单的算术问题,尽管如此,为了作个铺垫,我们还是把计算方法再叙述一下,以帮助读者理清思路。
有以下基本常识:
- 一年有 365 天,但闰年有 366 天;
- 一年有 12 个月,大月 31 天,小月 30 天;
- 2 月最特殊,普通年有 28 天,闰年有 29 天。
根据以上基本常识,计算步骤如下:
(1)计算从 1999 到 2006 期间共有多少个整年:
2000、2001、2002、2003、2004、2005,共有 6 个整年,其中 2000 和 2004 年是闰年,因此,共有 6×365+2=2192 天。
(2)计算从 1999 年 5 月 10 日到年底(即 12 月 31 日)共有多少天:
5 月 10 日到 12 月 31 日中共有 4 个整的大月(7 月、8 月、10 月、12 月),3 个整的小月(6 月、9 月、11 月),共记 4×31+3×30=214 天。
5 月 10 日到本月底(31 日)还有 31-10=21 天。
所以,1999 年 5 月 10 日到年底共有 214+21=235 天。
(3)计算从 2006 年元旦到 2006 年 3 月 8 日期间一共有多少天:
1 月有 31 天,2006 年不是闰年,2 月有 28 天,所以,总共有 31+28+8=67 天。
综上所述,1999 年 5 月 10 日到 2006 年 3 月 8 日期间一共有 2192+235+67=2494 天。
事实上,上述计算过程其实就是一个计算机算法(algorithm),由于步骤很明确,可以很容易地将这一过程转为程序。
编程之前,先将实际问题抽象为以下模型(图 1):
图 1 程序的最高层抽象
如图 1所示,我们要完成的工作就是开发出这样一个程序:程序接收两个日期值,经过计算之后,输出这两个日期之间的天数。
(1)结构化分析过程
为了清晰地表达出程序需要处理的信息,先定义一个结构体类型:
结构体类型 MyDate其实是定义了一种数据结构(data structure)。我们正是在这个数据结构之上构建出整个程序的。
对图 1所示的模型进行结构化分析的第一步,是将“程序”方框完成的功能转化为由一个函数 CalculateDaysOfTwoDate()实现:
余下的开发工作体现为给 CalculateDaysOfTwoDate()编写代码实现日期计算的功能。
在结构化编程中,有这样一个重要的公式:
程序=数据结构+算法
数据结构代表了要处理的信息,而算法则表明要对这些信息进行哪些处理工作。
只要确定了数据结构和算法,一个程序就成形了。因此,
将程序中要处理的数据抽象为某种数据结构是结构化编程的基础。
在本例中,算法非常简单,可以直接将人计算过程中的每一个步骤转为一个函数,由此得到以下两个函数:
//计算两年之间的整年天数,不足一年的去掉
static int CalculateDaysOfTwoYear(int beginYear, int endYear)
{
//……
}
//根据两个日期,计算出这两个日期之间的天数,不理会中间的整年
static int CalculateDaysOfTwoMonth(MyDate beginDate, MyDate endDate)
{
//……
}
第一个函数根据两个年份之间的整年数计算出天数,第二个函数根据月和日计算出两个日期之间的天数(不理会中间的整年)。
在深入地考虑这两个函数的具体实现算法时,会发现它们都需要判断一年是否是闰年,于是,设计另一个函数 IsLeapYear()完成此功能:
这样,函数 CalculateDaysOfTwoYear()和 CalculateDaysOfTwoMonth()在需要的时候即可调用 IsLeapYear()函数来判断是否某年为闰年。
至此设计工作完成,得到了以下结果(图 2):
CalculateDaysOfTwoDate
↙ ↘
CalculateDaysOfTwoMonth CalculateDaysOfTwoYear
↘ ↙
IsLeapYear
图 2结构化程序设计结果
图 2展示了结构化分析得到的设计方案,图中的箭头表示函数调用关系。
在整个结构化分析过程中,我们采用的是先设计出最顶层的 CalculateDaysOfTwoDate()函数的接口,再设计第二层的两个函数CalculateDaysOfTwoYear()和CalculateDaysOfTwoMonth(),最后抽象出第三层的函数 IsLeapYear()。
有了设计图,即可动手写代码。现在有四个函数需要开发,如何确定开发顺序?
很明显,必须先开发 IsLeapYear()函数,因为此函数被其他函数调用,但它不调用其他的函数。
接着可以开发 CalculateDaysOfTwoMonth()和 CalculateDaysOfTwoYear()两个函数,因为CalculateDaysOfTwoYear()函数比较简单,所以先开发它。
最后开发 CalculateDaysOfTwoDate()函数。
上述开发次序的确定可以用两句话来表达:
(1)盖楼先打地基:先开发最底层的函数,因为不完成开发这些函数,调用它们的上层函数就无法运行。
(2)柿子捡软的捏:在同一层次的函数中,先开发简单的,再开发复杂的。
函数开发完成之后,以下代码调用最顶层函数 CalculateDaysOfTwoDate()完成计算两日期之间天数的工作:
现在可以对结构化编程方法作个小结。
- 结构化软件系统的基本编程单位是函数。
- 整个系统按功能划分为若干个模块,每个模块都由逻辑上或功能上相关的若干个函数构成,各模块在功能上相对独立。
- 公用的函数存放在公用模块中,各模块间可以相互调用,拥有调用关系的模块形成一个树型结构,这种调用关系应尽可能做到是单向的。
结构化软件系统的架构如图 所示:
主控模块
↙ ↘
子模块 ↓ 子模块
↘ ↙
公用模块
结构化编程的开发过程可以分为以下三个阶段:
(1)分析阶段:在编程之前,需要仔细分析要解决的问题,确定好数据结构与算法。
(2)设计阶段:结构化编程的基本单元是函数,每个函数都完成整个程序的一个功能,整个设计过程就是函数接口的设计过程,这是一个“自顶向下,逐步求精”的过程,将一个
大函数不断分解为多个小函数,直至可以很容易用某种程序设计语言实现时为止。
(3)编码阶段:在开发时,根据在设计阶段得到的函数调用图,先开发最底层的函数,再开发上层函数。这是一个“自底向上,逐层盖楼”的方法。
结构化编程中“自顶向下,逐步求精”的“功能分解法”,是一种重要的软件开发方法,其本质是一种“分而治之”的思维方式,在面向对象的程序中也有广泛的应用。掌握这种分析方法,对软件工程师而言是非常重要的。
(2)面向对象分析过程
有了结构化分析的基础,可以很容易的将原先结构化的程序转为面向对象的程序。
创建一个 CalculateDate类,作为上面结构化分析得到的四个函数的“新家”,如图 所示:
图中将函数移到类中
由于外界只需要调用 CalculateDaysOfTwoDate()一个函数,所以将此函数设置为公有(public),而其他三个函数则成为类的私有(private)成员,外界不可访问.
以下为调用此类完成计算两个日期间天数的代码示例:
对比前面结构化的程序,不难发现面向对象的程序具有以下几个特点:
(1)所有的函数都放入到一个类中,成为某个类的成员,类是编程的基本单元。
(2)外界不能直接调用类的成员函数,必须先创建一个对象,再通过对象来调用这些函数。
(3)只有声明为 public的函数可以被外界调用(本例中为 CalculateDaysOfTwoDate()函数),其余声明为 private的函数是私有的,外界无法访问。
从这个实例可以看出,面向对象程序与结构化程序有着很不一样的风格,但看不出来面向对象有何优越之处。
的确,对于这个小实例而言,面向对象程序与结构化程序相比没有明显的优越性,而且显得更麻烦,但如果是大规模的软件系统,则面向对象程序就有着结构化程序不可比拟的优势,简单地说:
对于大规模的系统,采用面向对象技术开发可以达到较高的开发效率与较低的维护成本,系统的可扩展性也更好。
拿本节的小例子而言,其实.NET Framework本身就提供了两个类 DateTime和 TimeSpan可完成同样的功能:
对比一下,显然使用.NET Framework提供的现成类比我们手工编写代码完成同样的工作开发效率要高得多。.NET Framework中所提供的现成代码都是以面向对象的形式封装的。实践证明,当需要大规模地复用代码以提高软件生产率时,面向对象比结构化技术更有效。