|
我们的程序将自动判断名字形式,并且分解出姓和名。还是老规矩,请用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.继承
对象是用类来定义的。通过类,你能够充分了解对象的全貌。比如,一说起自行车,你就会联想到自行车是有两个轮子、车把以及脚踏板。
更进一步,面向对象语言的另一个特点便是允许从一个已有的类定义新的类。比如,山地车、公路赛车和两人三轮车都是自行车。在面向对象语言中,你可以从一个已经有的自行车类定义山地车类、公路赛车类等等。山地车类、公路赛车类都称为自行车类的子类,自行车类是它们的父类,而这种定义关系,便是继承关系。
子类继承了父类的属性。比如,山地车、公路赛车都是有两个轮子一个车?W永嘁部杉坛辛烁咐嗟姆椒ǎ?热缟降爻怠⒐?啡?怠⒘饺巳?殖刀伎梢郧敖?⑸渤怠⒆?涞取?BR>当然,子类并不限于继承,还可以发扬光大。比如两人三轮车便颠覆了自行车只有两个轮子、一个座垫的属性,使得自己更加休闲潇洒。
让我们看看如何运用继承来处理名在姓之前的模式。这种模式中,由于姓和名是用空格分割的,所以程序如下:
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年代以来最激动人心的革新之一”。然而,面向对象并非包治百病的灵丹妙药,其发展还远未成熟,还有许多问题值得我们付出真正的热情! |
|