`
yyzhpq
  • 浏览: 289920 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

(转)Java咖啡馆---大话面向对象

阅读更多
一、前世

    

  1946年2月15日,随着第一台现代电子计算机ENIAC轰鸣着来到这个世界,编写程序也成为三百六十行之外的一个崭新职业。我们称编写程序的工程师为程序员或者开发者。

  ENIAC是一台重达30吨的庞然大物,由19000多个晶体管、1500多个继电器组成。为了给它下达指令,程序员必须通过不同的连接线组合进行编程。要编制运行新的程序,还必须拔掉连线重新来过。整天面对二进制编程的工作相当枯燥乏味,而且是直接对程序地址读写,自然出错频繁。阅读由连线表达的程序更不亚于揣摩天书,维护和改造程序的价格成本居高不下。更要命的是,早期的计算机制造价格相当昂贵,而在程序编制调试完成之前,计算机不得不一直空转,导致软件开发的费用竟然远远超过硬件的投入。

  为了解决软件开发的难题,计算机科学家发明了汇编语言,通过一些助记符来减轻二进制编码的开发压力。这的确是行之有效的方法,直到现在,程序员在开发中还常常使用嵌入式汇编来提高软件运行速度,游戏引擎更是如此。然而,汇编语言太依赖程序员的素质,而且无法适应大规模的开发。

  黄糖故事 Grace Murray Hopper、Bug和Debug

  由于一次传奇般的投资,Mark I计算机把IBM从生产制表机、肉铺磅秤、咖啡碾磨机等乱七八糟玩意的行业,领入了计算机制造业的领地,最终成为如今的蓝色巨人。本系列文章中曾介绍过Mark I三个程序员之一的数学家Grace Hopper是如何创造了“BUG”和“DEBUG”这两个计算机史上著名的两个名词的。而这位Hopper女士,实在是一个不得了的人物。1952年,Hopper觉得用机器码编程是不是比较原始,为什么不能用类自然语言编写程序,然后再用一个工具把它转换成机器码呢?不久,她就开发出世界上第一套编译器A-0,是现代编译技术的原型。1956年她在第一台储存程序的商业电子计算机UNIVAC I、II上开发出B-0,之后叫做FLOW-MATIC,它导致了计算机商用语言COBOL(COmmon Business Oriented Language)的诞生。虽然Hopper有着“电脑之母”的美誉,但是传说她办公室有一个倒着走的钟,以及一面秀着骷髅头的海盗旗。

  到了六十年代,FORTRAN (FORmula TRANslating)、COBOL、LISP、ALGOL 60等现代高级语言的出现了。程序员可以用接近自然语言的程序语言编制软件,然后通过编译器转换成机器可执行的代码。由于使用精确的形式语言来定义程序语言本身,并且通过对硬件的抽象使得程序与计算机平台无关,导致高级语言生产效率大大提高,维护费用自然降低不少,计算机软件业终于得以蓬勃发展。

  好景不长。随着软件大规模的应用,程序的开发方法和管理手段逐渐无法跟上软件规模的膨胀,从而导致了软件危机的出现。就拿1963~1966年间的IBM 360系统来说,该系统有100万行的代码量,IBM每年动用5000人来维护该系统,但是,每个版本都是从上一个版本找出1000以上个错误而修订的结果,好像越改错误越多,根本没有改善的迹象。有人把IBM 360系统形容为一只逃亡的野兽落到泥潭中做垂死的挣扎,越是挣扎,陷的越深,最后仍然无法逃脱灭顶的灾难。

  人们不得不停下脚步思考,到底哪里出了问题。回想自己,每个人做事情,都是列举重点,然后细化并逐个完成。比如制造自行车,肯定是先把自行车按照功能分块,先造车架,然后是两个车轮,接着是踏板等传动装置,最后才是坐垫、车铃等零件。而制造车轮,肯定是要分别制造钢圈、钢丝、轮胎,而轮胎有分内外胎。如果软件开发能够遵循这种从大到小、逐步精确的思想,是不是能够解决这个软件危机呢?

  没错,这种结构化的抽象分析方法,导致了结构化程序设计方法的诞生。

  黄糖故事 Niklaus Wirth和PASCAL

  凡是学过一点计算机知识的人大概都知道“数据结构+算法二程序”这一著名公式。提出这一公式的瑞士计算机科学家Niklaus Wirth由于发明了多种影响深远的程序设计语言,并提出结构化程序设计这一革命性概念而获得了1984年的图灵奖。

  Wirth开发的PASCAL在数据结构和过程控制结构方面都有很多创造,比如Java中字符型、引用型,以及if-then-else、while、for等多种控制结构,都是从PASCAL里面借鉴发展而来的。可以说,现代程序设计语言中常用的数据结构和控制结构绝大多数都是由PASCAL语言奠定基础的,因此PASCAL在程序设计语言的发展史上具有承上启下的重要里程碑意义。现在你知道为什么很多计算机专业的学生都要学PASCAL语言了吧。

  1971年,Wirth基于其开发程序设计语言和编程的实践经验,首次提出了“结构化程序设计”(structured programming)的概念。这个概念的要点是:不要求一步就编制成可执行的程序,而是分若干步进行,逐步求精。第一步编出的程序抽象度最高,第二步编出的程序抽象度有所降低……最后一步编出的程序即为可执行的程序。用这种方法编程,似乎复杂,实际上优点很多,可使程序易读、易写、易调试、易维护、易保证其正确性及验证其正确性。结构化程序设计方法又称为“自顶向下”或“逐步求精”法,在程序设计领域引发了一场革命,成为程序开发的一个标准方法,尤其是在后来发展起来的软件工程中获得广泛应用。有人评价说沃思的结构化程序设计概念“完全改变了人们对程序设计的思维方式”,这是一点也不夸张的。

  黄糖故事 Philippe Kahn的Borland传奇

  Wirth开发PASCAL的初衷是为了有一个适合于教学的语言。但一经推出,由于它的简洁明了、提供丰富的数据结构和控制结构,使得程序开发大为简便,竟然大受欢迎。在C语言问世以前,PASCAL是风靡全球、最受欢迎的语言之一,不但创下了发行拷贝数最多的世界记录,而且成为大学数据结构教学的“惟一官方指定”语言。

  Phillipe Kahn是Niklaus Wirth的学生,毕业后到美国加利福尼亚州创立了Borland公司,凭借拳头产品Turbo PASCAL,当时就卖出了100多万个拷贝,成为百万富翁。而Borland公司是程序员津津乐道到程序开发工具供应商,他们从最早的Turbo PASCAL、Turbo C、Turbo PROLOG等Turbo系列,到如今的Delphi、C++ Builder、JBuilder、C# Builder系列,无一不是举足轻重的开发工具,从而在开发者心目中有着崇高的地位。

二、今生

    

  虽然结构化程序设计使得程序员世界观经历了巨大变革,行之有效地解决了软件开发中的许多问题,然而,结构化程序设计并不能完全解决软件危机,人们仍然渴望生产效率更高、更可靠、易维护、易管理的开发思想和开发方法。

  实际上,人们认识世界,是有一些基本的法则的:

  ?区分事物及其属性,如自行车和车子的颜色。
  ?区分整体对象及其组成部分,如区分自行车和车轮。
  ?不同对象类的形成及其区分,如山地自行车和两人休闲车虽然有相当的区别,但都属于自行车这个类型。

  心理学研究表明,把客观世界由许多对象组成,对象具有其属性和行为,之间存在着各种联系,这样能够更好的刻画问题域,也更接近人类的自然思维方式。这就是面向对象程序开发思想的由来。

  对象的概念最早出现于五十年代人工智能的早期著作中,而OO(面向对象)的实际发展始于1966年的Kisten Nygaard和Ole-Johan Dahl开发的Simula语言。正如名字昭示的,Simula可以模拟客观世界。比如在著名的银行出纳问题中,你可以创建若干个出纳员对象,若干个客户对象,还有若干钱对象以及交易对象(即把存款、提款等交易动作看成一个对象)?? 这个世界是由对象组成的。所有出纳员对象,除了各自的状态不同,都是属于的出纳员这个抽象类别。出纳员对象和客户对象之间通过消息传递进行交互,并且最终生成若干个交易对象,而交易对象可以操纵钱对象,完成存款或者提款的动作。

  你看,这个银行柜台世界,是不是完全可以由对象模拟呢?从而,面向对象设计程序,主要就是设计抽象的类。

  面向对象程序设计思想是一个里程碑。Alan Kay设计了世界上第一个完全面向对象的语言Smalltalk并成为图灵奖得主,Bjarne Stroustrup明智地把面向对象和最流行的C语言结合而开发了有史以来取得最大成功的C++语言,Anders Hejlsberg把PASCAL的面向对象版本Object PASCAL结合构件的思想开发出Windows平台上最优秀的快速程序开发(RAD)工具之一Delphi,James Gosling结合Internet背景开发了本咖啡馆赖以谋生计的Java语言,Bill Gates把.Net体系结构完全构筑在面向对象之上……

  黄糖故事 “面向对象”与“物件导向”

  阅读台湾技术作家的文章时经常会遇到“物件导向”一词。实际上,这是港澳台地区的计算机科学家对“Object Oriented”的翻译,与我们所说的“面向对象”是一回事情。不过,如果仔细从OO的理念品评一下两者的味道,似乎“物件导向”这个翻译更雅,更原汁原味。

  虽然面向对象只是从语法上引入为面向对象服务的封装、继承、多态等概念,但是必须看到,OO并非一种特殊的规定或者行业规范,而是一个优秀的理念,学习Java,应该把OO当作指导思想。

  构思这篇咖啡馆的时候时值奥运圣火熊熊燃烧,看到中国奥运代表团努力为国争光,不由不决定把这次的主题献给我们的奥运健儿!

面向对象编程

  如果你是Java咖啡馆的常客,那么在不知不觉中你早已接触并运用过Java的面向对象知识。在这回的咖啡馆中,让我们详细剖析一个面向对象编程的实例,把知识巩固下来。

  奥运是国际性的运动盛会,中国运动员自然要用英文形式的名字才便于同国际接轨。这回要编写的程序便是用来解析英文名字的工具。通常,Gary Chan这样的英文名字形式表示名在前、姓在后。而Yao, Ming这样的形式则表示姓在前名在后。大家千万不要以为Gary在这里平白无故用Yao, Ming打广告,Yao初中时候可是跟Gary一个班的,他被语文老师仰着头臭骂痛哭后,经常是Gary安慰他,并经常一同骑车回家。看着今日的Yao已经是世界级的运动员为国争光了,Gary更需要加倍努力了……

  OK,言归正传,我们的程序将自动判断名字形式,并且分解出姓和名。还是老规矩,请用Eclipse生成一个名为Chap 07 NameParser的项目,并且加入一个新的名为NameParser类,在Package属性填写com.cfan.garychan.nameparser。如果你忘记了package的知识,请参考《Java咖啡馆(6)—编写猜数字游戏 》中关于包概念的描述。

  回顾一下,类是定义了从类生成的实例(instance)中的数据和方法的关系的模板。有人喜欢把类比作图章,图章敲出来的图案便是对象,的确很形象。

  Java中用class关键字来定义类,不过我们用Eclipse来定义更加方便。仍然用Eclipse新建一个叫做Namer的类,记得不要在public static void main(String[] args)前面打勾,确定后Eclipse便生成一个新的Java源文件Namer.java,里面的代码如下:

public class Namer {

}

  这个类非常简单,可惜不能做任何事情。

  1.封装

  面向对象程序设计中,一个非常重要的技术便是封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。这样做的好处在于可以使类内部的具体实现透明化,只要其他代码不依赖类内部的私房数据,你便可以安心修改这些代码。此外,这样做也是出于安全方面的考虑,如果代表网上支付卡密码的变量随便就可以被访问到,这样的系统谁还敢用呢?

  封装主要依靠对类、数据和方法的访问控制,从语法上讲就是加上private、protected、public等关键词,如果没有关键词修饰则默认为package。它们控制权限如下表所示:

Specifier 类 子类 包 世界
private X
protected X X* X
public X X X X
package X X

  注意上面的X*,父类的protected部分,只有在与父类在同一个包内的子类才能够访问,否则也是不可访问的。

  让我们结合实例理解一下。稍微把Namer类改一下:

public class Namer {
    protected String surname;  // 姓
    protected String firstname; // 名

    public String getFirstname() {
        return firstname;
    }

    public String getSurname() {
        return surname;
    }
}

  这个类有两个String类型的成员变量,surname和firstname,分别用来储存姓和名。这两个成员变量前都有protected修饰词,按照表格,这两个变量仅能够被类本身、子类以及包中其他类操作,而包外的类则无权访问。不过,为了跟包外的代码进行沟通,Namer类提供了getFirstname和getSurname这两个public的方法。从而,对包外的类而言,姓名数据是只读的。

  2.继承

  对象是用类来定义的。通过类,你能够充分了解对象的全貌。比如,一说起自行车,你就会联想到自行车是有两个轮子、车把以及脚踏板。

  更进一步,面向对象语言的另一个特点便是允许从一个已有的类定义新的类。比如,山地车、公路赛车和两人三轮车都是自行车。在面向对象语言中,你可以从一个已经有的自行车类定义山地车类、公路赛车类等等。山地车类、公路赛车类都称为自行车类的子类,自行车类是它们的父类,而这种定义关系,便是继承关系。

  子类继承了父类的属性。比如,山地车、公路赛车都是有两个轮子一个车座。子类也可继承了父类的方法,比如山地车、公路赛车、两人三轮车都可以前进、刹车、转弯等。
当然,子类并不限于继承,还可以发扬光大。比如两人三轮车便颠覆了自行车只有两个轮子、一个座垫的属性,使得自己更加休闲潇洒。

  让我们看看如何运用继承来处理名在姓之前的模式。这种模式中,由于姓和名是用空格分割的,所以程序如下:

class FirstFirst extends Namer {
    public FirstFirst(String s) {
        int i = s.lastIndexOf(" "); // 搜索空格
        if (i > 0) {
            firstname = s.substring(0, i).trim();
            surname = s.substring(i + 1).trim();
        }
    }
}

  FirstFirst类通过extends关键词表示对Namer类进行继承,只有一个类方法,名字恰好是FirstFirst。这并不是一个巧合。

  所有的Java类都拥有若干特殊方法用来初始化对象,它们称为构造函数,特征就是与类同名,可以带有或者没有参数。这种同名函数不同参数的现象,在面向对象中称作重载(Overload)。拿以前使用new操作符生成随机数的代码来说:

Random random = new Random();

  new操作符实例化一个Random对象后,紧接着就调用了Random类的构造函数进行初始化,只不过这个构造函数没有参数。没有参数的构造函数,称为默认构造函数。默认的构造函数是每个类都拥有的,即使没有声明在代码中,Java编译器在编译时也会自动加入。

  回过头来看FirstFirst类。FirstFirst类继承自Namer类,从而也拥有自己的firstname和surname属性。在FirstFirst类的构造函数中,通过解析参数s,通过搜索空格的方法来解析出空格前面的名和空格后面的姓,从而执行

FirstFirst parser = new FirstFirst("Gary Chan");

  之后,我的姓和名已经解析出来并且分别保存在firstname和surname变量中了。同时,FirstFirst类继承了Namer的方法,从而便可以通过如下语句来返回姓——Gary了:

String mySername = parser.getSurname();

  注意,我们并没有在FirstFirst类中定义getSurname()方法,这是从父类继承来的,这就是代码重用的概念,避免了无谓的重复劳动。

  有了上面的基础,再来编写名在姓之后的模式:

class FirstLast extends Namer {
    public FirstLast(String s) {
        int i = s.indexOf(","); // 搜索逗号
        if (i > 0) {
            surname = s.substring(0, i).trim();
            firstname = s.substring(i + 1).trim();
        }
    }
}

  由此可见,Namer类的两个子类拥有它全部的属性和方法,并且在其之上更加入了解析姓名的能力,而代码却增加不多。代码重用,这是面向对象的主要魅力之一!

  3.多态

  至此,我们已经分别为两种名字解析方法编写了两个类,即FirstLast类和FirstFirst类。为了更好地使用这两个类,让我们玩一些小技巧。

  首先,对于姓名解析器的使用者,具体是使用Namer类还是FirstLast类还是FirstFirst类,他是不关心的。这些东西最好都是自动化的,他只要能得到姓和名即可。
其次,如果你是属于胆大心细遇事不慌的(阿庆嫂类型)IT青年的话,一定会发现Namer.java中只有Namer类是public的,FirstFirst类和FirstLast类之前没有修饰——它们是默认的package的,也就是说,在com.cfan.garychan.nameparser包之外,都是无法被访问到的。

  如果仅仅能够Namer类来解析姓名那该多好啊!

  实际上,运用多态的概念,这些问题将迎刃而解。

  面向对象一共有三个特性:封装、继承、多态。所谓封装,就是通过定义类并且给类的属性和方法加上访问控制来抽象事物的本质特性。所谓继承,就是代码重用。而多态,从另外一个角度分割了接口和实现,即把“什么”和“如何”两个概念分离开来。举个例子,公路赛车是自行车,继承了自行车的刹车方法。假设你和朋友骑着捷安特的公路赛车出游,当你的朋友正好侧着脸看风景时,前面突然窜出来一只猫,你一定大声惊呼:赶快刹车!仔细体会这句话,你的意识中只是知道自行车可以刹车,所以让朋友按下车闸让自行车刹车,而绝对不是认为—捷安特牌子的公路赛车赶快刹车!从而,思考的是抽象的
自行车的刹车,而最终动作却是捷安特牌子的公路赛车刹车,通过类指代实例,这就是多态的概念。

  回过头看我们的程序,public的Namer类正好是FirstFirst类和FirstLast类的共同父类,应用多态的概念实在是太合适不过了。新建一个名为NameFactory的类,并且把这个类也放在com.cfan.garychan.nameparser包中,代码如下:

public class NameFactory {
    public static Namer getNamer(String entry) {
    if (entry.indexOf(",") > 0)
        return new FirstLast(entry); //return one class
    else if (entry.indexOf(" ") > 0)
        return new FirstFirst(entry); //or the other
    else
        return null;
    }
}

  NameFactory类只有一个静态方法getNamer,注意返回的是一个Namer类。下面根据entry参数是否包含“,”符号来确定实际生成的是FirstLast类还是FirstFirst类,最终将其返回。你看,说是返回Namer类,实际返回的是FirstLast类或者FirstFirst类,这就是多态的典型应用。需要注意的是,并非毫不相关的类都能够当作多态使用,必须是有继承关系,而且有方向性。结合生活经验,多态的概念并不难理解。

  最后让我们看看如何使用这个姓名解析器。新建Chap07NameParser类,Package是com.cfan.garychan,代码如下:

package com.cfan.garychan;

import com.cfan.garychan.nameparser.NameFactory;
import com.cfan.garychan.nameparser.Namer;

/**
* 用解析器解析姚明的英文名字。
*/
public class Chap07NameParser {
    public static void main(String[] args) {
        Namer namer = NameFactory.getNamer("Yao, Ming");
        if (null == namer) {
            System.out.println("姓名不合法");
        else {
            System.out.println("姓:" + namer.getSurname());
            System.out.println("名:" + namer.getFirstname());
        }
    }
}

  你看,我们通过NameFactory返回一个Namer对象,这个对象能够解析姚明的英文名字,你不必关心这个Namer对象究竟是FirstFirst类还是FirstLast类,方便极了。

面向对象的未来

  面向对象技术是软件技术自然演变的结果,在许多领域有着强大的生命力与美好的前景。借用Maurice Wilkes在他的图灵奖领奖仪式上的话,“面向对象技术是70年代以来最激动人心的革新之一”。然而,面向对象并非包治百病的灵丹妙药,其发展还远未成熟,还有许多问题值得我们付出真正的热情!

<script type="text/javascript"> &amp;amp;amp;amp;amp;lt;!-- google_ad_client = "pub-3051157228350391"; google_alternate_color = "FF0000"; google_ad_width = 336; google_ad_height = 280; google_ad_format = "336x280_as"; google_ad_channel ="9050871643"; google_page_url = document.location; google_color_border = "CCCCCC"; google_color_bg = "FFFFFF"; google_color_link = "000000"; google_color_url = "666666"; google_color_text = "333333"; //--&amp;amp;amp;amp;amp;gt; </script><script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script><iframe name="google_ads_frame" scrolling="no" marginheight="0" allowtransparency="65535" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-3051157228350391&amp;amp;dt=1112786052046&amp;amp;prev_fmts=336x280_as&amp;amp;format=336x280&amp;amp;output=html&amp;amp;u_h=768&amp;amp;u_w=1024&amp;amp;u_ah=740&amp;amp;u_aw=1024&amp;amp;u_cd=32&amp;amp;u_tz=480&amp;amp;u_his=3&amp;amp;u_java=true" marginwidth="0" frameborder="0" height="280" width="336"></iframe><iframe name="google_ads_frame" scrolling="no" marginheight="0" allowtransparency="65535" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-3051157228350391&amp;amp;dt=1112786057593&amp;amp;prev_fmts=336x280_as%2C336x280&amp;amp;format=336x280&amp;amp;output=html&amp;amp;u_h=768&amp;amp;u_w=1024&amp;amp;u_ah=740&amp;amp;u_aw=1024&amp;amp;u_cd=32&amp;amp;u_tz=480&amp;amp;u_his=3&amp;amp;u_java=true" marginwidth="0" frameborder="0" height="280" width="336"></iframe>
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics