# 16.4 改進設計
《設計模式》書內所有方案的組織都圍繞“程序進化時會發生什么變化”這個問題展開。對于任何設計來說,這都可能是最重要的一個問題。若根據對這個問題的回答來構造自己的系統,就可以得到兩個方面的結果:系統不僅更易維護(而且更廉價),而且能產生一些能夠重復使用的對象,進而使其他相關系統的構造也變得更廉價。這正是面向對象程序設計的優勢所在,但這一優勢并不是自動體現出來的。它要求對我們對需要解決的問題有全面而且深入的理解。在這一節中,我們準備在系統的逐步改進過程中向大家展示如何做到這一點。
就目前這個回收系統來說,對“什么會變化”這個問題的回答是非常普通的:更多的類型會加入系統。因此,設計的目標就是盡可能簡化這種類型的添加。在回收程序中,我們準備把涉及特定類型信息的所有地方都封裝起來。這樣一來(如果沒有別的原因),所有變化對那些封裝來說都是在本地進行的。這種處理方式也使代碼剩余的部分顯得特別清爽。
## 16.4.1 “制作更多的對象”
這樣便引出了面向對象程序設計時一條常規的準則,我最早是在Grady Booch那里聽說的:“若設計過于復雜,就制作更多的對象”。盡管聽起來有些曖昧,且簡單得可笑,但這確實是我知道的最有用一條準則(大家以后會注意到“制作更多的對象”經常等同于“添加另一個層次的迂回”)。一般情況下,如果發現一個地方充斥著大量繁復的代碼,就需要考慮什么類能使它顯得清爽一些。用這種方式整理系統,往往會得到一個更好的結構,也使程序更加靈活。
首先考慮Trash對象首次創建的地方,這是`main()`里的一個`switch`語句:
```
for(int i = 0; i < 30; i++)
switch((int)(Math.random() * 3)) {
case 0 :
bin.addElement(new
Aluminum(Math.random() * 100));
break;
case 1 :
bin.addElement(new
Paper(Math.random() * 100));
break;
case 2 :
bin.addElement(new
Glass(Math.random() * 100));
}
```
這些代碼顯然“過于復雜”,也是新類型加入時必須改動代碼的場所之一。如果經常都要加入新類型,那么更好的方案就是建立一個獨立的方法,用它獲取所有必需的信息,并創建一個引用,指向正確類型的一個對象——已經向上轉換到一個`Trash`對象。在《設計模式》中,它被粗略地稱呼為“創建模式”。要在這里應用的特殊模式是`Factory`方法的一種變體。在這里,`Factory`方法屬于`Trash`的一名`static`(靜態)成員。但更常見的一種情況是:它屬于派生類中一個被重載的方法。
`Factory`方法的基本原理是我們將創建對象所需的基本信息傳遞給它,然后返回并等候引用(已經向上轉換至基類型)作為返回值出現。從這時開始,就可以按多態性的方式對待對象了。因此,我們根本沒必要知道所創建對象的準確類型是什么。事實上,`Factory`方法會把自己隱藏起來,我們是看不見它的。這樣做可防止不慎的誤用。如果想在沒有多態性的前提下使用對象,必須明確地使用RTTI和指定轉換。
但仍然存在一個小問題,特別是在基類中使用更復雜的方法(不是在這里展示的那種),且在派生類里重載(覆蓋)了它的前提下。如果在派生類里請求的信息要求更多或者不同的參數,那么該怎么辦呢?“創建更多的對象”解決了這個問題。為實現`Factory`方法,`Trash`類使用了一個新的方法,名為`factory`。為了將創建數據隱藏起來,我們用一個名為`Info`的新類包含`factory`方法創建適當的`Trash`對象時需要的全部信息。下面是`Info`一種簡單的實現方式:
```
class Info {
int type;
// Must change this to add another type:
static final int MAX_NUM = 4;
double data;
Info(int typeNum, double dat) {
type = typeNum % MAX_NUM;
data = dat;
}
}
```
`Info`對象唯一的任務就是容納用于`factory()`方法的信息。現在,假如出現了一種特殊情況,`factory()`需要更多或者不同的信息來新建一種類型的`Trash`對象,那么再也不需要改動`factory()`了。通過添加新的數據和構造器,我們可以修改`Info`類,或者采用子類處理更典型的面向對象形式。
用于這個簡單示例的`factory()`方法如下:
```
static Trash factory(Info i) {
switch(i.type) {
default: // To quiet the compiler
case 0:
return new Aluminum(i.data);
case 1:
return new Paper(i.data);
case 2:
return new Glass(i.data);
// Two lines here:
case 3:
return new Cardboard(i.data);
}
}
```
在這里,對象的準確類型很容易即可判斷出來。但我們可以設想一些更復雜的情況,`factory()`將采用一種復雜的算法。無論如何,現在的關鍵是它已隱藏到某個地方,而且我們在添加新類型時知道去那個地方。
新對象在`main()`中的創建現在變得非常簡單和清爽:
```
for(int i = 0; i < 30; i++)
bin.addElement(
Trash.factory(
new Info(
(int)(Math.random() * Info.MAX_NUM),
Math.random() * 100)));
```
我們在這里創建了一個`Info`對象,用于將數據傳入`factory()`;后者在內存堆中創建某種T`rash`對象,并返回添加到`Vector bin`內的引用。當然,如果改變了參數的數量及類型,仍然需要修改這個語句。但假如`Info`對象的創建是自動進行的,也可以避免那個麻煩。例如,可將參數的一個`Vector`傳遞到`Info`對象的構造器中(或直接傳入一個`factory()`調用)。這要求在運行期間對參數進行分析與檢查,但確實提供了非常高的靈活程度。
大家從這個代碼可看出`Factory`要負責解決的“領頭變化”問題:如果向系統添加了新類型(發生了變化),唯一需要修改的代碼在`Factory`內部,所以`Factory`將那種變化的影響隔離出來了。
## 16.4.2 用于原型創建的一個模式
上述設計模式的一個問題是仍然需要一個中心場所,必須在那里知道所有類型的對象:在`factory()`方法內部。如果經常都要向系統添加新類型,`factory()`方法為每種新類型都要修改一遍。若確實對這個問題感到苦惱,可試試再深入一步,將與類型有關的所有信息——包括它的創建過程——都移入代表那種類型的類內部。這樣一來,每次新添一種類型的時候,需要做的唯一事情就是從一個類繼承。
為將涉及類型創建的信息移入特定類型的Trash里,必須使用“原型”(`prototype`)模式(來自《設計模式》那本書)。這里最基本的想法是我們有一個主控對象序列,為自己感興趣的每種類型都制作一個。這個序列中的對象只能用于新對象的創建,采用的操作類似內建到Java根類`Object`內部的`clone()`機制。在這種情況下,我們將克隆方法命名為`tClone()`。準備創建一個新對象時,要事先收集好某種形式的信息,用它建立我們希望的對象類型。然后在主控序列中遍歷,將手上的信息與主控序列中原型對象內任何適當的信息作對比。若找到一個符合自己需要的,就克隆它。
采用這種方案,我們不必用硬編碼的方式植入任何創建信息。每個對象都知道如何揭示出適當的信息,以及如何對自身進行克隆。所以一種新類型加入系統的時候,`factory()`方法不需要任何改變。
為解決原型的創建問題,一個方法是添加大量方法,用它們支持新對象的創建。但在Java 1.1中,如果擁有指向`Class`對象的一個引用,那么它已經提供了對創建新對象的支持。利用Java 1.1的“反射”(已在第11章介紹)技術,即便我們只有指向`Class`對象的一個引用,亦可正常地調用一個構造器。這對原型問題的解決無疑是個完美的方案。
原型列表將由指向所有想創建的`Class`對象的一個引用列表間接地表示。除此之外,假如原型處理失敗,則`factory()`方法會認為由于一個特定的`Class`對象不在列表中,所以會嘗試裝載它。通過以這種方式動態裝載原型,`Trash`類根本不需要知道自己要操縱的是什么類型。因此,在我們添加新類型時不需要作出任何形式的修改。于是,我們可在本章剩余的部分方便地重復利用它。
```
//: Trash.java
// Base class for Trash recycling examples
package c16.trash;
import java.util.*;
import java.lang.reflect.*;
public abstract class Trash {
private double weight;
Trash(double wt) { weight = wt; }
Trash() {}
public abstract double value();
public double weight() { return weight; }
// Sums the value of Trash in a bin:
public static void sumValue(Vector bin) {
Enumeration e = bin.elements();
double val = 0.0f;
while(e.hasMoreElements()) {
// One kind of RTTI:
// A dynamically-checked cast
Trash t = (Trash)e.nextElement();
val += t.weight() * t.value();
System.out.println(
"weight of " +
// Using RTTI to get type
// information about the class:
t.getClass().getName() +
" = " + t.weight());
}
System.out.println("Total value = " + val);
}
// Remainder of class provides support for
// prototyping:
public static class PrototypeNotFoundException
extends Exception {}
public static class CannotCreateTrashException
extends Exception {}
private static Vector trashTypes =
new Vector();
public static Trash factory(Info info)
throws PrototypeNotFoundException,
CannotCreateTrashException {
for(int i = 0; i < trashTypes.size(); i++) {
// Somehow determine the new type
// to create, and create one:
Class tc =
(Class)trashTypes.elementAt(i);
if (tc.getName().indexOf(info.id) != -1) {
try {
// Get the dynamic constructor method
// that takes a double argument:
Constructor ctor =
tc.getConstructor(
new Class[] {double.class});
// Call the constructor to create a
// new object:
return (Trash)ctor.newInstance(
new Object[]{new Double(info.data)});
} catch(Exception ex) {
ex.printStackTrace();
throw new CannotCreateTrashException();
}
}
}
// Class was not in the list. Try to load it,
// but it must be in your class path!
try {
System.out.println("Loading " + info.id);
trashTypes.addElement(
Class.forName(info.id));
} catch(Exception e) {
e.printStackTrace();
throw new PrototypeNotFoundException();
}
// Loaded successfully. Recursive call
// should work this time:
return factory(info);
}
public static class Info {
public String id;
public double data;
public Info(String name, double data) {
id = name;
this.data = data;
}
}
} ///:~
```
基本`Trash`類和`sumValue()`還是象往常一樣。這個類剩下的部分支持原型模式。大家首先會看到兩個內部類(被設為`static`屬性,使其成為只為代碼組織目的而存在的內部類),它們描述了可能出現的異常。在它后面跟隨的是一個`Vector trashTypes`,用于容納`Class`引用。
在`Trash.factory()`中,`Info`對象`id`(`Info`類的另一個版本,與前面討論的不同)內部的`String`包含了要創建的那種`Trash`的類型名稱。這個`String`會與列表中的`Class`名比較。若存在相符的,那便是要創建的對象。當然,還有很多方法可以決定我們想創建的對象。之所以要采用這種方法,是因為從一個文件讀入的信息可以轉換成對象。
發現自己要創建的`Trash`(垃圾)種類后,接下來就輪到“反射”方法大顯身手了。`getConstructor()`方法需要取得自己的參數——由`Class`引用構成的一個數組。這個數組代表著不同的參數,并按它們正確的順序排列,以便我們查找的構造器使用。在這兒,該數組是用Java 1.1的數組創建語法動態創建的:
```
new Class[] {double.class}
```
這個代碼假定所有`Trash`類型都有一個需要`double`數值的構造器(注意`double.class`與`Double.class`是不同的)。若考慮一種更靈活的方案,亦可調用`getConstructors()`,令其返回可用構造器的一個數組。
從`getConstructors()`返回的是指向一個`Constructor`對象的引用(該對象是`java.lang.reflect`的一部分)。我們用方法`newInstance()`動態地調用構造器。該方法需要獲取包含了實際參數的一個`Object`數組。這個數組同樣是按Java 1.1的語法創建的:
```
new Object[] {new Double(info.data)}
```
在這種情況下,`double`必須置入一個封裝(容器)類的內部,使其真正成為這個對象數組的一部分。通過調用`newInstance()`,會提取出`double`,但大家可能會覺得稍微有些迷惑——參數既可能是`double`,也可能是`Double`,但在調用的時候必須用`Double`傳遞。幸運的是,這個問題只存在于基本數據類型中間。
理解了具體的過程后,再來創建一個新對象,并且只為它提供一個`Class`引用,事情就變得非常簡單了。就目前的情況來說,內部循環中的`return`永遠不會執行,我們在終點就會退出。在這兒,程序動態裝載`Class`對象,并把它加入`trashTypes`(垃圾類型)列表,從而試圖糾正這個問題。若仍然找不到真正有問題的地方,同時裝載又是成功的,那么就重復調用`factory`方法,重新試一遍。
正如大家會看到的那樣,這種設計模式最大的優點就是不需要改動代碼。無論在什么情況下,它都能正常地使用(假定所有`Trash`子類都包含了一個構造器,用以獲取單個`double`參數)。
(1) Trash子類
為了與原型機制相適應,對`Trash`每個新子類唯一的要求就是在其中包含了一個構造器,指示它獲取一個`double`參數。Java 1.1的“反射”機制可負責剩下的所有工作。
下面是不同類型的`Trash`,每種類型都有它們自己的文件里,但都屬于`Trash`包的一部分(同樣地,為了方便在本章內重復使用):
```
//: Aluminum.java
// The Aluminum class with prototyping
package c16.trash;
public class Aluminum extends Trash {
private static double val = 1.67f;
public Aluminum(double wt) { super(wt); }
public double value() { return val; }
public static void value(double newVal) {
val = newVal;
}
} ///:~
```
下面是一種新的`Trash`類型:
```
//: Cardboard.java
// The Cardboard class with prototyping
package c16.trash;
public class Cardboard extends Trash {
private static double val = 0.23f;
public Cardboard(double wt) { super(wt); }
public double value() { return val; }
public static void value(double newVal) {
val = newVal;
}
} ///:~
```
可以看出,除構造器以外,這些類根本沒有什么特別的地方。
(2) 從外部文件中解析出`Trash`
與`Trash`對象有關的信息將從一個外部文件中讀取。針對`Trash`的每個方面,文件內列出了所有必要的信息——每行都代表一個方面,采用`垃圾(廢品)名稱:值`的固定格式。例如:
```
c16.Trash.Glass:54
c16.Trash.Paper:22
c16.Trash.Paper:11
c16.Trash.Glass:17
c16.Trash.Aluminum:89
c16.Trash.Paper:88
c16.Trash.Aluminum:76
c16.Trash.Cardboard:96
c16.Trash.Aluminum:25
c16.Trash.Aluminum:34
c16.Trash.Glass:11
c16.Trash.Glass:68
c16.Trash.Glass:43
c16.Trash.Aluminum:27
c16.Trash.Cardboard:44
c16.Trash.Aluminum:18
c16.Trash.Paper:91
c16.Trash.Glass:63
c16.Trash.Glass:50
c16.Trash.Glass:80
c16.Trash.Aluminum:81
c16.Trash.Cardboard:12
c16.Trash.Glass:12
c16.Trash.Glass:54
c16.Trash.Aluminum:36
c16.Trash.Aluminum:93
c16.Trash.Glass:93
c16.Trash.Paper:80
c16.Trash.Glass:36
c16.Trash.Glass:12
c16.Trash.Glass:60
c16.Trash.Paper:66
c16.Trash.Aluminum:36
c16.Trash.Cardboard:22
```
注意在給定類名的時候,類路徑必須包含在內,否則就找不到類。
為解析它,每一行內容都會讀入,并用字符串方法`indexOf()`來建立`:`的一個索引。首先用字符串方法`substring()`取出垃圾的類型名稱,接著用一個靜態方法`Double.valueOf()`取得相應的值,并轉換成一個`double`值。`trim()`方法則用于刪除字符串兩頭的多余空格。
`Trash`解析器置入單獨的文件中,因為本章將不斷地用到它。如下所示:
```
//: ParseTrash.java
// Open a file and parse its contents into
// Trash objects, placing each into a Vector
package c16.trash;
import java.util.*;
import java.io.*;
public class ParseTrash {
public static void
fillBin(String filename, Fillable bin) {
try {
BufferedReader data =
new BufferedReader(
new FileReader(filename));
String buf;
while((buf = data.readLine())!= null) {
String type = buf.substring(0,
buf.indexOf(':')).trim();
double weight = Double.valueOf(
buf.substring(buf.indexOf(':') + 1)
.trim()).doubleValue();
bin.addTrash(
Trash.factory(
new Trash.Info(type, weight)));
}
data.close();
} catch(IOException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
// Special case to handle Vector:
public static void
fillBin(String filename, Vector bin) {
fillBin(filename, new FillableVector(bin));
}
} ///:~
```
在`RecycleA.java`中,我們用一個`Vector`容納`Trash`對象。然而,亦可考慮采用其他集合類型。為做到這一點,`fillBin()`的第一個版本將獲取指向一個`Fillable`的引用。后者是一個接口,用于支持一個名為`addTrash()`的方法:
```
//: Fillable.java
// Any object that can be filled with Trash
package c16.trash;
public interface Fillable {
void addTrash(Trash t);
} ///:~
```
支持該接口的所有東西都能伴隨`fillBin`使用。當然,`Vector`并未實現`Fillable`,所以它不能工作。由于`Vector`將在大多數例子中應用,所以最好的做法是添加另一個重載的`fillBin()`方法,令其以一個`Vector`作為參數。利用一個適配器(`Adapter`)類,這個`Vector`可作為一個`Fillable`對象使用:
```
//: FillableVector.java
// Adapter that makes a Vector Fillable
package c16.trash;
import java.util.*;
public class FillableVector implements Fillable {
private Vector v;
public FillableVector(Vector vv) { v = vv; }
public void addTrash(Trash t) {
v.addElement(t);
}
} ///:~
```
可以看到,這個類唯一的任務就是負責將`Fillable`的`addTrash()`同`Vector`的`addElement()`方法連接起來。利用這個類,已重載的`fillBin()`方法可在`ParseTrash.java`中伴隨一個`Vector`使用:
```
public static void
fillBin(String filename, Vector bin) {
fillBin(filename, new FillableVector(bin));
}
```
這種方案適用于任何頻繁用到的集合類。除此以外,集合類還可提供它自己的適配器類,并實現`Fillable`(稍后即可看到,在`DynaTrash.java`中)。
(3) 原型機制的重復應用
現在,大家可以看到采用原型技術的、修訂過的`RecycleA.java`版本了:
```
//: RecycleAP.java
// Recycling with RTTI and Prototypes
package c16.recycleap;
import c16.trash.*;
import java.util.*;
public class RecycleAP {
public static void main(String[] args) {
Vector bin = new Vector();
// Fill up the Trash bin:
ParseTrash.fillBin("Trash.dat", bin);
Vector
glassBin = new Vector(),
paperBin = new Vector(),
alBin = new Vector();
Enumeration sorter = bin.elements();
// Sort the Trash:
while(sorter.hasMoreElements()) {
Object t = sorter.nextElement();
// RTTI to show class membership:
if(t instanceof Aluminum)
alBin.addElement(t);
if(t instanceof Paper)
paperBin.addElement(t);
if(t instanceof Glass)
glassBin.addElement(t);
}
Trash.sumValue(alBin);
Trash.sumValue(paperBin);
Trash.sumValue(glassBin);
Trash.sumValue(bin);
}
} ///:~
```
所有`Trash`對象——以及`ParseTrash`及支撐類——現在都成為名為`c16.trash`的一個包的一部分,所以它們可以簡單地導入。
無論打開包含了`Trash`描述信息的數據文件,還是對那個文件進行解析,所有涉及到的操作均已封裝到`static`(靜態)方法`ParseTrash.fillBin()`里。所以它現在已經不是我們設計過程中要注意的一個重點。在本章剩余的部分,大家經常都會看到無論添加的是什么類型的新類,`ParseTrash.fillBin()`都會持續工作,不會發生改變,這無疑是一種優良的設計模式。
提到對象的創建,這一方案確實已將新類型加入系統所需的變動嚴格地“本地化”了。但在使用RTTI的過程中,卻存在著一個嚴重的問題,這里已明確地顯露出來。程序表面上工作得很好,但卻永遠偵測到不能“硬紙板”(`Cardboard`)這種新的廢品類型——即使列表里確實有一個硬紙板類型!之所以會出現這種情況,完全是由于使用了RTTI的緣故。RTTI只會查找那些我們告訴它查找的東西。RTTI在這里錯誤的用法是“系統中的每種類型”都進行了測試,而不是僅測試一種類型或者一個類型子集。正如大家以后會看到的那樣,在測試每一種類型時可換用其他方式來運用多態性特征。但假如以這種形式過多地使用RTTI,而且又在自己的系統里添加了一種新類型,很容易就會忘記在程序里作出適當的改動,從而埋下以后難以發現的Bug。因此,在這種情況下避免使用RTTI是很有必要的,這并不僅僅是為了表面好看——也是為了產生更易維護的代碼。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物