Skip to content

Commit 65d12d9

Browse files
committed
add
Signed-off-by: maskleo <[email protected]>
1 parent 3790d89 commit 65d12d9

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed

Diff for: ch09/05_1.png

43.9 KB
Loading

Diff for: ch09/05_Subject-Observer.md

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
## 主题观察者
2+
3+
我们用一个更加扩展的例子来完成,说明通用的 `Subject-Observer` 模式。与策略模式一样,`Subject-Observer` 模式使用并行类层次结构,但这次我们需要两个具有相互递归边界的类型变量,一个代表特定类型的主体,一个代表特定类型的观察者。这是我们的第一个带有相互递归边界的类型变量的例子。
4+
5+
`Java` 库在包 `java.util` 中使用 `Observable` 类和接口 `Observer`(前者对应于主题)实现了非通用版本的 `Subject-Observer` 模式,其签名示例如图 `9-8` 所示。
6+
7+
`Observable` 类包含注册观察者(`addObserver`)的方法,指示 `observable` 已更改(`setChanged`),并通知所有观察者有任何更改(`notifyObservers`)等等。 `notifyObservers` 方法可以接受将被广播给所有观察者的 `Object` 类型的任意参数。 `Observer` 接口指定由 `notifyObservers` 调用的更新方法。这个方法有两个参数:第一个是 `Observable` 类型,它是已经改变的主体;第二种类型的对象是广播参数。
8+
9+
对象在方法签名中的出现常常表明有机会进行基因组化。 因此,我们应该期望通过添加与参数类型相对应的类型参数 `A` 来生成类。 此外,我们可以用 `Observable``Observer` 自己替换类型参数 `S``O`(用于 `Subject``Observer`)。 然后在观察者的更新方法中,您可以调用主体 `S` 支持的任何方法,而无需先投射。
10+
11+
`9-9` 显示了如何为 `Observable` 类和 `Observer` 接口指定相应的通用签名。 这里是相关的标题:
12+
13+
```java
14+
public class Observable<S extends Observable<S,O,A>, O extends Observer<S,O,A>,A>
15+
public interface Observer<S extends Observable<S,O,A>, O extends Observer<S,O,A>,A>
16+
```
17+
18+
这两个声明都采用相同的三个类型参数。这些声明是有趣的,因为它们说明了类型参数的范围可以是相互递归的:所有三个类型参数都出现在前两个的范围内。在此之前,我们看到了简单递归的其他例子,例如 `Comparable` 和 `Enum` 的声明,以及前面关于 `Strategy` 模式的部分。但这是我们第一次看到相互递归。
19+
20+
检查声明的主体,可以看到 `O` 但不是 `S` 出现在 `Observable` 类的主体中,并且 `S` 但不是 `O` 出现在 `Observer` 接口的主体中。所以你可能会想:通过从 `Observable` 中删除类型参数 `S` 和 `Observer` 中的类型参数 `O`,可以简化声明吗?但是这是行不通的,因为你需要 `S` 在 `Observable` 的范围内,所以它可以作为参数传递给 `Observer`,并且你需要 `O` 在 `Observer` 的范围内,以便它可以作为参数传递给 `Observable`。
21+
22+
如 `5.4.2` 节所述,通用声明使用存根。我们根据 `Observable` 和 `Observer` 的通用签名来编译客户端,但是针对标准 `Java` 分发中的类文件运行代码。我们使用存根,因为我们不想对库的源进行任何更改,因为它由 `Sun` 维护。
23+
24+
例 `9-8`。泛型之前的可观察和观察者
25+
26+
```java
27+
package java.util;
28+
public class Observable {
29+
public void addObserver(Observer o) {...}
30+
protected void clearChanged() {...}
31+
public int countObservers() {...}
32+
public void deleteObserver(Observer o) {...}
33+
public boolean hasChanged() {...}
34+
public void notifyObservers() {...}
35+
public void notifyObservers(Object arg) {...}
36+
protected void setChanged() {...}
37+
}
38+
39+
package java.util;
40+
public interface Observer {
41+
public void update(Observable o, Object arg);
42+
}
43+
```
44+
45+
例9-9。 泛型的可观察和观察者
46+
47+
```java
48+
package java.util;
49+
class StubException extends UnsupportedOperationException {}
50+
public class Observable<S extends Observable<S,O,A>, O extends Observer<S,O,A>, A> {
51+
public void addObserver(O o) { throw new StubException(); }
52+
protected void clearChanged() { throw new StubException(); }
53+
public int countObservers() { throw new StubException(); }
54+
public void deleteObserver(O o) { throw new StubException(); }
55+
public boolean hasChanged() { throw new StubException(); }
56+
public void notifyObservers() { throw new StubException(); }
57+
public void notifyObservers(A a) { throw new StubException(); }
58+
protected void setChanged() { throw new StubException(); }
59+
}
60+
package java.util;
61+
public interface Observer<S extends Observable<S,O,A>, O extends Observer<S,O,A>, A> {
62+
public void update(S o, A a);
63+
}
64+
```
65+
66+
作为 `Observable``Observer` 的演示客户端,示例 `9-10` 介绍了货币转换器。 转换器的屏幕截图如图 `9-1` 所示。 转换器允许您为三种货币(美元,欧元和英镑)中的每一种输入转换率,并以任何货币输入一个值。 更改费率条目会导致重新计算相应的值; 更改值的条目会导致重新计算所有值。
67+
68+
客户端通过声明 `CModel``Observable` 的子类并将 `CView` 作为 `Observer` 的子接口来实例化模式。 此外,参数类型被实例化为 `Currency`,一个枚举类型,可用于通知观察者该主题的哪些组件已更改。 这里是相关的标题:
69+
70+
```java
71+
enum Currency { DOLLAR, EURO, POUND }
72+
class CModel extends Observable<CModel, CView, Currency>
73+
interface CView extends Observer<CModel, CView, Currency>
74+
```
75+
76+
RateViewValueView实现CView,类Converter定义控制显示的顶层框架。
77+
78+
CModel类有一个方法来设置和获取给定货币的汇率和价值。汇率存储在一个映射中,该映射为每种货币分配一个汇率,并存储该值(作为一个长,
79+
80+
![](05_1.png)
81+
82+
图 `9-1`。 货币换算
83+
84+
以美分,欧分或便士)与其实际货币。 要计算给定货币的价值,该值除以实际货币的利率并乘以给定货币的利率。
85+
86+
每当费率发生变化时,`CModel` 类将调用 `RateView` 的更新方法,并将相应货币作为参数传递(因为只需更新货币的费率和价值); 当值更改时,它会调用 `ValueView` 的更新方法,并将 `null` 作为参数传递(因为需要更新所有货币的值)。
87+
88+
我们编译并运行代码如下。 首先,我们编译 `Observable` 和 `Observer` 的通用版本:
89+
90+
```java
91+
% javac -d . java/util/Observable.java java/util/Observer.java
92+
```
93+
94+
由于这些包位于 `java.util` 包中,它们必须保存在当前目录的子目录 `java/util` 中。 其次,我们在包 `com.eg.converter` 中编译 `Converter` 和相关的类。 默认情况下,`Java` 编译器首先搜索当前目录中的类文件(即使是标准库)。 因此,编译器使用为 `Observable` 和 `Observer` 生成的存根类文件,它们具有正确的通用签名(但不包含可运行代码):
95+
96+
```java
97+
% javac -d . com/eg/converter/Converter.java
98+
```
99+
100+
第三,我们运行转换器的类文件。 默认情况下,`java` 运行时不会首先在当前目录中搜索包 `java` 和 `javax` 中的类文件 - 由于安全原因,它们始终从标准库中获取。 因此,运行时使用 `Observable` 和 `Observer` 的标准类文件,其中包含我们想要运行的遗留代码(但没有正确的通用签名):
101+
102+
```java
103+
% java com.eg.converter.Converter
104+
```
105+
106+
例9-10。 货币换算
107+
108+
```java
109+
import java.util.*;
110+
import javax.swing.*;
111+
import javax.swing.event.*;
112+
import java.awt.*;
113+
import java.awt.event.*;
114+
enum Currency { DOLLAR, EURO, POUND }
115+
class CModel extends Observable<CModel,CView,Currency> {
116+
private final EnumMap<Currency,Double> rates;
117+
private long value = 0; // cents, euro cents, or pence
118+
private Currency currency = Currency.DOLLAR;
119+
public CModel() {
120+
rates = new EnumMap<Currency,Double>(Currency.class);
121+
}
122+
public void initialize(double... initialRates) {
123+
for (int i=0; i<initialRates.length; i++)
124+
setRate(Currency.values()[i], initialRates[i]);
125+
}
126+
public void setRate(Currency currency, double rate) {
127+
rates.put(currency, rate);
128+
setChanged();
129+
notifyObservers(currency);
130+
}
131+
public void setValue(Currency currency, long value) {
132+
this.currency = currency;
133+
this.value = value;
134+
setChanged();
135+
notifyObservers(null);
136+
}
137+
public double getRate(Currency currency) {
138+
return rates.get(currency);
139+
}
140+
public long getValue(Currency currency) {
141+
if (currency == this.currency)
142+
return value;
143+
else
144+
return Math.round(value * getRate(currency) / getRate(this.currency));
145+
}
146+
}
147+
interface CView extends Observer<CModel,CView,Currency> {}
148+
class RateView extends JTextField implements CView {
149+
private final CModel model;
150+
private final Currency currency;
151+
public RateView(final CModel model, final Currency currency) {
152+
this.model = model;
153+
this.currency = currency;
154+
addActionListener(new ActionListener() {
155+
public void actionPerformed(ActionEvent e) {
156+
try {
157+
double rate = Double.parseDouble(getText());
158+
model.setRate(currency, rate);
159+
} catch (NumberFormatException x) {}
160+
}
161+
});
162+
model.addObserver(this);
163+
}
164+
public void update(CModel model, Currency currency) {
165+
if (this.currency == currency) {
166+
double rate = model.getRate(currency);
167+
setText(String.format("%10.6f", rate));
168+
}
169+
}
170+
}
171+
class ValueView extends JTextField implements CView {
172+
private final CModel model;
173+
private final Currency currency;
174+
public ValueView(final CModel model, final Currency currency) {
175+
this.model = model;
176+
this.currency = currency;
177+
addActionListener(new ActionListener() {
178+
public void actionPerformed(ActionEvent e) {
179+
try {
180+
long value = Math.round(100.0*Double.parseDouble(getText()));
181+
model.setValue(currency, value);
182+
} catch (NumberFormatException x) {}
183+
}
184+
});
185+
model.addObserver(this);
186+
}
187+
public void update(CModel model, Currency currency) {
188+
if (currency == null || currency == this.currency) {
189+
long value = model.getValue(this.currency);
190+
setText(String.format("%15d.%02d", value/100, value%100));
191+
}
192+
}
193+
}
194+
class Converter extends JFrame {
195+
public Converter() {
196+
CModel model = new CModel();
197+
setTitle("Currency converter");
198+
setLayout(new GridLayout(Currency.values().length+1, 3));
199+
add(new JLabel("currency"));
200+
add(new JLabel("rate"));
201+
add(new JLabel("value"));
202+
for (Currency currency : Currency.values()) {
203+
add(new JLabel(currency.name()));
204+
add(new RateView(model, currency));
205+
add(new ValueView(model, currency));
206+
}
207+
model.initialize(1.0, 0.83, 0.56);
208+
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
209+
pack();
210+
}
211+
public static void main(String[] args) {
212+
new Converter().setVisible(true);
213+
}
214+
}
215+
```
216+
217+
因此,当我们使用标准库类的存根时,我们不需要修改类路径,就像我们在 `5.4.2` 节中所做的那样,因为默认情况下会获得正确的行为。 (如果您确实想在运行时更改标准库类,则可以使用 `-Xbootclass` 路径标志。)
218+
219+
这就结束了我们对泛型的讨论。 现在,您已经有了一个全面的基础,可以使用其他人定义的通用库,定义自己的库,将遗留代码发展为通用代码,了解对泛型的限制并避免陷阱,在需要时使用检查和专业化,以及在设计模式中利用泛型。
220+
221+
泛型最重要的用途之一是集合框架,在本书的下一部分,我们将向您展示如何有效地使用此框架并提高您作为 `Java` 程序员的工作效率。
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+
238+

0 commit comments

Comments
 (0)