友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第101部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
1。 设置注册表
在这儿,大家可看到对静态方法Naming。bind() 的一个调用。然而,这个调用要求注册表作为计算机上的一
个独立进程运行。注册表服务器的名字是 rmiregistry。在32 位Windows 环境中,可使用:
start rmiregistry
令其在后台运行。在Unix 中,使用:
rmiregistry &
和许多网络程序一样,rmiregistry 位于机器启动它所在的某个 IP 地址处,但它也必须监视一个端口。如果
象上面那样调用rmiregistry,不使用参数,注册表的端口就会默认为 1099。若希望它位于其他某个端口,
只需在命令行添加一个参数,指定那个端口编号即可。对这个例子来说,端口将位于 2005,所以
rmiregistry 应该象下面这样启动(对于32 位Windows):
start rmiregistry 2005
对于Unix ,则使用下述命令:
rmiregistry 2005 &
与端口有关的信息必须传送给bind()命令,同时传送的还有注册表所在的那台机器的 IP 地址。但假若我们
想在本地测试RMI 程序,就象本章的网络程序一直测试的那样,这样做就会带来问题。在 JDK 1。1。1 版本
中,存在着下述两方面的问题(注释⑦):
(1) localhost不能随 RMI 工作。所以为了在单独一台机器上完成对 RMI 的测试,必须提供机器的名字。为
了在32 位 Windows 环境中调查自己机器的名字,可进入控制面板,选择“网络”,选择“标识”卡片,其中
列出了计算机的名字。就我自己的情况来说,我的机器叫作“Colossus ”(因为我用几个大容量的硬盘保存
各种不同的开发系统——Clossus 是“巨人”的意思)。似乎大写形式会被忽略。
(2) 除非计算机有一个活动的TCP/IP 连接,否则 RMI 不能工作,即使所有组件都只需要在本地机器里互相通
信。这意味着在试图运行程序之前,必须连接到自己的 ISP (因特网服务提供者),否则会得到一些含义模
糊的违例消息。
⑦:为找出这些信息,我不知损伤了多少个脑细胞。
考虑到这些因素,bind()命令变成了下面这个样子:
Naming。bind(〃//colossus:2005/PerfectTime〃; pt);
若使用默认端口 1099,就没有必要指定一个端口,所以可以使用:
Naming。bind(〃//colossus/PerfectTime〃; pt);
在JDK 未来的版本中(1。1之后),一旦改正了 localhost 的问题,就能正常地进行本地测试,去掉 IP地
584
…………………………………………………………Page 586……………………………………………………………
址,只使用标识符:
Naming。bind(〃PerfectTime〃; pt);
服务名是任意的;它在这里正好为 PerfectTime,和类名一样,但你可以根据情况任意修改。最重要的是确
保它在注册表里是个独一无二的名字,以便客户正常地获取远程对象。若这个名字已在注册表里了,就会得
到一个AlreadyBoundException 违例。为防止这个问题,可考虑坚持使用 rebind() ,放弃bind()。这是由于
rebind()要么会添加一个新条目,要么将同名的条目替换掉。
尽管main()退出,我们的对象已经创建并注册,所以会由注册表一直保持活动状态,等候客户到达并发出对
它的请求。只要rmiregistry 处于运行状态,而且我们没有为名字调用 Naming。unbind()方法,对象就肯定
位于那个地方。考虑到这个原因,在我们设计自己的代码时,需要先关闭 rmiregistry,并在编译远程对象
的一个新版本时重新启动它。
并不一定要将rmiregistry 作为一个外部进程启动。若事前知道自己的是要求用以注册表的唯一一个应用,
就可在程序内部启动它,使用下述代码:
LocateRegistry。createRegistry(2005);
和前面一样,2005 代表我们在这个例子里选用的端口号。这等价于在命令行执行 rmiregistry 2005。但在设
计RMI 代码时,这种做法往往显得更加方便,因为它取消了启动和中止注册表所需的额外步骤。一旦执行完
这个代码,就可象以前一样使用Naming 进行“绑定”——bind()。
15。8。3 创建根与干
若编译和运行PerfectTime。java,即使rmiregistry 正确运行,它也无法工作。这是由于RMI 的框架尚未就
位。首先必须创建根和干,以便提供网络连接操作,并使我们将远程对象伪装成自己机器内的某个本地对
象。
所有这些幕后的工作都是相当复杂的。我们从远程对象传入、传出的任何对象都必须“implement
Serializable”(如果想传递远程引用,而非整个对象,对象的参数就可以“implement Remote”)。因此
可以想象,当根和干通过网络“汇集”所有参数并返回结果的时候,会自动进行序列化以及数据的重新装
配。幸运的是,我们根本没必要了解这些方面的任何细节,但根和干却是必须创建的。一个简单的过程如
下:在编译好的代码中调用rmic,它会创建必需的一些文件。所以唯一要做的事情就是为编译过程新添一个
步骤。
然而,rmic 工具与特定的包和类路径有很大的关联。PerfectTime。java 位于包 c15。Ptime 中,即使我们调用
与PerfectTime。class 同一目录内的rmic,rmic 都无法找到文件。这是由于它搜索的是类路径。因此,我们
必须同时指定类路径,就象下面这样:
rmic c15。PTime。PerfectTime
执行这个命令时,并不一定非要在包含了 PerfectTime。class 的目录中,但结果会置于当前目录。
若rmic 成功运行,目录里就会多出两个新类:
PerfectTime_Stub。class
PerfectTime_Skel。class
它们分别对应根(Stub )和干(Skeleton )。现在,我们已准备好让服务器与客户互相沟通了。
15。8。4 使用远程对象
RMI 全部的宗旨就是尽可能简化远程对象的使用。我们在客户程序中要做的唯一一件额外的事情就是查找并
从服务器取回远程接口。自此以后,剩下的事情就是普通的Java 编程:将消息发给对象。下面是使用
PerfectTime 的程序:
//: DisplayPerfectTime。java
// Uses remote object PerfectTime
package c15。ptime;
import java。rmi。*;
import java。rmi。registry。*;
public class DisplayPerfectTime {
public static void main(String'' args) {
System。setSecurityManager(
585
…………………………………………………………Page 587……………………………………………………………
new RMISecur ityManager());
try {
PerfectTimeI t =
(PerfectTimeI)Naming。lookup(
〃//colossus:2005/PerfectTime〃);
for(int i = 0; i 《 10; i++)
System。out。println(〃Perfect time = 〃 +
t。getPerfectTime());
} catch(Exception e) {
e。printStackTrace();
}
}
} ///:~
ID字串与那个用 Naming 注册对象的那个字串是相同的,第一部分指出了 URL 和端口号。由于我们准备使用
一个URL,所以也可以指定因特网上的一台机器。
从Naming。lookup()返回的必须造型到远程接口,而不是到类。若换用类,会得到一个违例提示。
在下述方法调用中:
t。getPerfectTime( )
我们可看到一旦获得远程对象的句柄,用它进行的编程与用本地对象的编程是非常相似(仅有一个区别:远
程方法会“掷”出一个 RemoteException 违例)。
15。8。5 RMI 的替选方案
RMI 只是一种创建特殊对象的方式,它创建的对象可通过网络发布。它最大的优点就是提供了一种“纯
Java ”方案,但假如已经有许多用其他语言编写的代码,则RMI 可能无法满足我们的要求。目前,两种最具
竞争力的替选方案是微软的D (根据微软的计划,它最终会移植到除Windows 以外的其他平台)以及
CORBA。CORBA 自Java 1。1 便开始支持,是一种全新设计的概念,面向跨平台应用。在由 Orfali 和 Harkey
编著的《Client/Server Programming with Java and CORBA》一书中(John Wiley&Sons 1997 年出版),
大家可获得对Java 中的分布式对象的全面介绍(该书似乎对 CORBA 似乎有些偏见)。为CORBA 赋予一个较公
正的对待的一本书是由 Andreas Vogel 和Keith Duddy 编写的《Java Programming with CORBA》,John
Wiley&Sons 于 1997 年出版。
15。9 总结
由于篇幅所限,还有其他许多涉及连网的概念没有介绍给大家。Java 也为 URL 提供了相当全面的支持,包括
为因特网上不同类型的客户提供协议控制器等等。
除此以外,一种正在逐步流行的技术叫作Servlet Server。它是一种因特网服务器应用,通过Java 控制客
户请求,而非使用以前那种速度很慢、且相当麻烦的CGI (通用网关接口)协议。这意味着为了在服务器那
一端提供服务,我们可以用Java 编程,不必使用自己不熟悉的其他语言。由于 Java 具有优秀的移植能力,
所以不必关心具体容纳这个服务器是什么平台。
所有这些以及其他特性都在《Java Network Progr amming》一书中得到了详细讲述。该书由 Elliotte Rusty
Harold 编著,O'Reilly 于 1997 年出版。
15。10 练习
(1) 编译和运行本章中的JabberServer 和 JabberClient 程序。接着编辑一下程序,删去为输入和输出设计
的所有缓冲机制,然后再次编译和运行,观察一下结果。
(2) 创建一个服务器,用它请求用户输入密码,然后打开一个文件,并将文件通过网络连接传送出去。创建
一个同该服务器连接的客户,为其分配适当的密码,然后捕获和保存文件。在自己的机器上用 localhost
(通过调用InetAddress。getByName(null)生成本地 IP地址 127。0。0。1)测试这两个程序。
(3) 修改练习2 中的程序,令其用多线程机制对多个客户进行控制。
(4) 修改JabberClient,禁止输出刷新,并观察结果。
(5) 以ShowHTML。java 为基础,创建一个程序片,令其成为对自己Web 站点的特定部分进行密码保护的大
586
…………………………………………………………Page 588……………………………………………………………
门。
(6) (可能有些难度)创建一对客户/服务器程序,利用数据报(Datagram )将一个文件从一台机器传到另
一台(参见本章数据报小节末尾的叙述)。
(7) (可能有些难度)对VLookup。java 程序作一番修改,使我们能点击得到的结果名字,然后程序会自动取
得那个名字,并把它复制到剪贴板(以便我们方便地粘贴到自己的E…mail )。可能要回过头去研究一下IO
数据流的那一章,回忆该如何使用 Java 1。1 剪贴板。
587
…………………………………………………………Page 589……………………………………………………………
第 16 章 设计范式
本章要向大家介绍重要但却并不是那么传统的“范式”(Pattern)程序设计方法。
在向面向对象程序设计的演化过程中,或许最重要的一步就是“设计范式”(Design Pattern)的问世。它
在由Gamma,Helm 和 Johnson 编著的《Design Patterns》一书中被定义成一个“里程碑”(该书由
Addison…Wesley 于 1995 年出版,注释①)。那本书列出了解决这个问题的 23 种不同的方法。在本章中,我
们准备伴随几个例子揭示出设计范式的基本概念。这或许能激起您阅读《Design Pattern》一书的欲望。事
实上,那本书现在已成为几乎所有 OOP 程序员都必备的参考书。
①:但警告大家:书中的例子是用 C++写的。
本章的后一部分包含了展示设计进化过程的一个例子,首先是比较原始的方案,经过逐渐发展和改进,慢慢
成为更符合逻辑、更为恰当的设计。该程序(仿真垃圾分类)一直都在进化,可将这种进化作为自己设计方
案的一个原型——先为特定的问题提出一个适当的方案,再逐步改善,使其成为解决那类问题一种最灵活的
方案。16。1 范式的概念
在最开始,可将范式想象成一种特别聪明、能够自我适应的手法,它可以解决特定类型的问题。也就是说,
它类似一些需要全面认识某个问题的人。在了解了问题的方方面面以后,最后提出一套最通用、最灵活的解
决方案。具体问题或许是以前见到并解决过的。然而,从前的方案也许并不是最完善的,大家会看到它如何
在一个范式里具体表达出来。
尽管我们称之为“设计范式”,但它们实际上并不局限于设计领域。思考“范式”时,应脱离传统意义上分
析、设计以及实施的思考方式。相反,“范式”是在一个程序里具体表达一套完整的思想,所以它有时可能
出现在分析阶段或者高级设计阶段。这一点是非常有趣的,因为范式具有以代码形式直接实现的形式,所以
可能不希望它在低级设计或者具体实施以前显露出来(而且事实上,除非真正进入那些阶段,否则一般意识
不到自己需要一个范式来解决问题)。
范式的基本概念亦可看成是程序设计的基本概念:添加一层新的抽象!只要我们抽象了某些东西,就相当于
隔离了特定的细节。而且这后面最引人注目的动机就是“将保持不变的东西身上发生的变化孤立出来”。这
样做的另一个原因是一旦发现程序的某部分由于这样或那样的原因可能发生变化,我们一般都想防止那些改
变在代码内部繁衍出其他变化。这样做不仅可以降低代码的维护代价,也更便于我们理解(结果同样是降低
开销)。
为设计出功能强大且易于维护的应用项目,通常最困难的部分就是找出我称之为“领头变化”的东西。这意
味着需要找出造成系统改变的最重要的东西,或者换一个角度,找出付出代价最高、开销最大的那一部分。
一旦发现了“领头变化”,就可以为自己定下一个焦点,围绕它展开自己的设计。
所以设计范式的最终目标就是将代码中变化的内容隔离开。如果从这个角度观察,就会发现本书实际已采用
了一些设计范式。举个例子来说,继承可以想象成一种设计范式(类似一个由编译器实现的)。在都拥有同
样接口(即保持不变的东西)的对象内部,它允许我们表达行为上的差异(即发生变化的东西)。合成亦可
想象成一种范式,因为它允许我们修改——动态或静态——用于实现类的对象,所以也能修改类的运作方
式。
在《Design Patterns》一书中,大家还能看到另一种范式:“继承器”(即Iterator,Java 1。0 和 1。1 不
负责任地把它叫作Enumeration,即“枚举”;Java1。2 的集合则改回了“继承器”的称呼)。当我们在集合
里遍历,逐个选择不同的元素时,继承器可将集合的实施细节有效地隐藏起来。利用继承器,可以编写出通
用的代码,以便对一个序列里的所有元素采取某种操作,同时不必关心这个序列是如何构建的。这样一来,
我们的通用代码即可伴随任何能产生继承器的集合使用。
16。1。1 单子
或许最简单的设计范式就是“单子”(Singleton),它能提供对象的一个(而且只有一个)实例。单子在
Java 库中得到了应用,但下面这个例子显得更直接一些:
//: SingletonPattern。java
// The Singleton design pattern: you can
// never instantiate more than one。
588
…………………………………………………………Page 590……………………………………………………………
package c16;
// Since this isn't inherited from a Cloneable
// base class and cloneability isn't added;
// making it final prevents cloneability from
// being added in any derived classes:
final class Singleton {
private static Singleton s = new Singleton(47);
private int i;
private Singleton(int x) { i = x; }
public static Singleton getHandle() {
return s;
}
public int getValue() { return i; }
public void setValue(int x) { i = x; }
}
public class SingletonPattern {
public static void main(String'' args) {
Singleton s = Singleton。getHandle();
System。out。println(s。getValue());
Singleton s2 = Singleton。getHandle();
s2。setValue(9);
System。out。println(s。getValue());
try {
// Can't do this: pile…time error。
// Singleton s3 = (Singleton)s2。clone();
} catch(Exception e) {}
}
} ///:~
创建单子的关键就是防止客户程序员采用除由我们提供的之外的任何一种方式来创建一个对象。必须将所有
构建器都设为private (私有),而且至少要创建一个构建器,以防止编译器帮我们自动同步一个默认构建
器(它会自做聪明地创建成为“友好的”——friendly,而非 private)。
此时应决定如何创建自己的对象。在这儿,我们选择了静态创建的方式。但亦可选择等候客户程序员发出一
个创建请求,然后根据他们的要求动态创建。不管在哪种情况下,对象都应该保存为“私有”属性。我们通
过公用方法提供访问途径。在这里,getHandle()会产生指向 Singleton 的一个句柄。剩下的接口
(getValue()和 setValue())属于普通的类接口。
Java 也允许通过克隆(Clone)方式来创建一个对象。在这个例子中,将类设为 final 可禁止克隆的发生。
由于Singleton 是从Object 直接继承的,所以 clone()方法会保持 protected (受保护)属性,不能够使用
它(强行使用会造成编译期错误)。然而,假如我们是从一个类结构中继承,那个结构已经过载了clone()
方法,使其具有public 属性,并实现了Cloneable ,那么为了禁止克隆,需要过载clone(),并掷出一个
CloneNotSupportedException (不支持克隆违例),就象第12章介绍的那样。亦可过载 clone(),并简单地
返回 this。那样做会造成一定的混淆,因为客户程序员可能错误地认为对象尚未克隆,仍然操纵的是原来的
那个。
注意我们并不限于只能创建一个对象。亦可利用该技术创建一个有限的对象池。但在那种情况下,可能需要
解决池内对象的共享问题。如果不幸真的遇到这个问题,可以自己设计一套方案,实现共享对象的登记与撤
消登记。
16。1。2 范式分类
《Design Patterns》一书讨论了23 种不同的范式,并依据三个标准分类(所有标准都涉及那些可能发生变
化的方面)。这三个标准是:
589
…………………………………………………………Page 591……………………………………………………………
(1) 创建:对象的创建方式。这通常涉及对象创建细节的�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!