關(guān)鍵字:Observer Pattern、Java Thread、Java Swing Application
1 近來的閱讀
近來寒暑不常,希自珍慰。武漢天氣不是狂冷,就是狂熱,不時(shí)還給我整個(gè)雪花,就差冰雹了。
自己做的事吧,也沒有什么勁兒。看看自己喜歡的東西,等著希望中的學(xué)校能給我offers(是復(fù)數(shù)),看著自己想去又不想去的公司的未來同事在群里面幻想未來的樣子,別操你大爺了,都成人這么久了,咋就還不了解這個(gè)世道呢?你給老板賺十塊錢,老板自然會(huì)給你一塊。如果老板的周圍會(huì)賺錢的人不多,你還可以嘗試吆喝更高的價(jià)錢。就這么個(gè)事,幻想有個(gè)鳥用。還不如靜下心來好好學(xué)點(diǎn)兒東西,想想將來怎么多多給老板賺錢。無論以后打工還是自己當(dāng)老板,路都得這么走出來。
另外,就是思考的畢設(shè)的事情。畢竟,讓我立刻狠心的fuck掉這個(gè)學(xué)位也是不現(xiàn)實(shí)的。所以,繼續(xù)搞,搞一個(gè)題目叫做“基于復(fù)雜網(wǎng)絡(luò)的可度量防垃圾郵件系統(tǒng)”的論文加實(shí)驗(yàn)系統(tǒng)。看名字很BT,可我覺得這已經(jīng)是我覺得最有意思的畢設(shè)題目了。我一定要做出點(diǎn)東西來。
無聊看書加上要完成畢設(shè)的實(shí)驗(yàn)系統(tǒng),于是有了些動(dòng)力,就產(chǎn)生了本篇小隨筆。權(quán)當(dāng)備忘吧。
看著“觀察者模式”,琢磨琢磨,感覺跟別的模式有那么一點(diǎn)不一樣。但是又說不出來,感覺使用這個(gè)模式的環(huán)境,為多線程為多。然后,就轉(zhuǎn)而去看線程的書,看著線程的書,又發(fā)現(xiàn)畢設(shè)要用的Swing是學(xué)習(xí)理解線程很好的素材,而且Swing中的event-dispatch機(jī)制就是“觀察者模式”,于是又轉(zhuǎn)而去看講Swing的書及專欄文章。
到此,這3個(gè)東西是徹底扯在一起了。總結(jié)一下它們之間的關(guān)系:Swing是建立在event-dispatch機(jī)制及Swing Single Thread下的多線程GUI編程環(huán)境。當(dāng)然,在很多情況下,你寫Swing也不大會(huì)用到(察覺)多線程(如我們的java課本中的Swing例子),但是,使用它的可能性已經(jīng)大大增加了。而且要寫好Swing,不理解線程是很困難的。
2 Java Thread
2.1 操作系統(tǒng)及面向過程
對(duì)于我這個(gè)新手來講,使用多線程的機(jī)會(huì)不多。天天搞J2EE的應(yīng)用開發(fā),也根本用不上這個(gè)東西。但是,不理解你,不使用你,何談功力的提升呢。你不找我,我也要找你。
在面向?qū)ο蟮氖澜缰校@個(gè)Thread讓我真的不好理解。它仿佛和我們自己寫的對(duì)象,JDK的別的API類庫(kù)都不一樣,咋一看,看不出它的“使用價(jià)值”。(我理解的對(duì)象都有很“明顯”的“使用價(jià)值”,呵呵)。
因?yàn)楦嗟卣務(wù)摗斑M(jìn)程”、“線程”還是在操作系統(tǒng)層面,記得在OS課本中有好幾個(gè)章節(jié)都是講“進(jìn)程”相關(guān)的知識(shí):調(diào)度算法、調(diào)度隊(duì)列、調(diào)度優(yōu)先級(jí)等等。后來上Unix和Linux的時(shí)候重點(diǎn)更是folk和進(jìn)程間通訊(IPC)機(jī)制。但都是停留在理論理解,應(yīng)付考試的階段。而且編寫的代碼也都是面向過程的風(fēng)格,沒有“對(duì)象”的感覺。
Java能把OS層的概念加進(jìn)來并予以實(shí)現(xiàn),功能上很強(qiáng),另外也給新手接觸底層編程的機(jī)會(huì)。但是,把OS層的線程概念及它的面向過程編寫風(fēng)格與面向?qū)ο笃毡榈娘L(fēng)格一比較,對(duì)Java Thread對(duì)象的理解就有點(diǎn)迷糊了。
2.2 從接口的本質(zhì)思考
一般寫程序不會(huì)太關(guān)注main(),特別是J2EE的東西搞多了,更是把它給忘盡了。main是程序的開始,是JVM的主線程。線程是另一個(gè)main(這樣理解肯定不準(zhǔn)確,但是有極大雷同),是欲并行執(zhí)行的程序代碼序列。
在java中,如果在main之外,還要執(zhí)行獨(dú)立并行的程序代碼序列,就要用到java線程了。方法也比較簡(jiǎn)單:把獨(dú)立執(zhí)行的代碼序列寫到一個(gè)Runnable對(duì)象的run方法中,然后在父進(jìn)程(很有可能就是main線程)中start它(先要把Runnable變成Thread對(duì)象)就可以了。
接口描述的是一種功能,而不是事物本質(zhì)(類來描述)。如果我們把“能獨(dú)立并行執(zhí)行”當(dāng)作一個(gè)對(duì)象的能力的話,把線程概念融入面向?qū)ο缶腿菀锥嗔恕F鸫a我是這么理解的。
至于你是喜歡給已有的類添加一個(gè)這個(gè)功能(也就是讓已有的類來實(shí)現(xiàn)Runnable接口),還是另外專門寫一個(gè)類,專門封裝這種功能。就見仁見智了。正如我都喜歡把main獨(dú)立寫在一個(gè)控制類中,而不是放在功能類中一樣。
2.3 匿名的嵌套類
也正是因?yàn)榫€程的并行性本質(zhì),在我們的代碼中,那里要并行執(zhí)行,我們就在這里開一個(gè)新的線程。而這時(shí),為了最簡(jiǎn)便,對(duì)代碼結(jié)構(gòu)影響最小,最常見方式的就是開一個(gè)匿名的嵌套線程。
例子:如果doNoSequence()需要并行于執(zhí)行doJob()的線程而獨(dú)立執(zhí)行。
1
Void doJob()
{
2
3
doFirst();
6
7
//need to be excuted in concurrently.
9
doNoSequence();
11
13
doSecond();
14
15
}
16
就可以為它開一個(gè)匿名的嵌套線程。
1
Void doJob()
{
2
doFirst();
3
4
new Thread()
{
5
public void run()
{
6
doNoSequence();
7
}
8
}.start();
9
10
doSecond();
11
}
12
2.4 異步的本質(zhì),同步的需要
“獨(dú)立并行”的感覺一般開始都很好,至于以后,呵呵,不好說。
特別是當(dāng)系統(tǒng)已經(jīng)設(shè)計(jì)好了以后,在代碼中按上面的例子隨便開新線程。看似操作容易,但如果開的線程一多,相互之間的聯(lián)系一多,以后就很難理解、管理和維護(hù)(許多article作者的話,我沒有經(jīng)驗(yàn))。
doNoSequence()現(xiàn)在就開始難受了,因?yàn)樗l(fā)現(xiàn)它和主線程還有很多聯(lián)系,如和doFirst()有共同access的對(duì)象;還如doSecond()接受它返回的參數(shù),一定要等它返回才能繼續(xù)執(zhí)行(也就是doNoSequence()對(duì)于主線程是block的)。明顯,這里都需要“同步”。
當(dāng)然,也不必絕望,只不過在代碼里面繼續(xù)添加更多的我不熟練的線程控制代碼。線程開的越多,線程有關(guān)的關(guān)鍵字和控制代碼就會(huì)出現(xiàn)的更多。最終的代碼也許會(huì)面目全非,越來越難看出核心邏輯了。線程代碼多了是小,但是核心的業(yè)務(wù)邏輯被淹沒了是不允許的。
最好的解決方法,當(dāng)然就是在設(shè)計(jì)的時(shí)候,多考慮一下程序并行化的可能。把有可能并行的邏輯分開來寫。換句話講,也就是要增加并行程序內(nèi)部的耦合性,降低并行程序之間的耦合性。以便將來并不并行都容易使用。
如要編寫既能異步執(zhí)行,又能相對(duì)容易實(shí)現(xiàn)同步控制的代碼,不少人(當(dāng)然,都是大牛人)推薦使用“觀察者模式”來重構(gòu)你的系統(tǒng)。
3 觀察者模式
“觀察者模式(Observer Pattern)”是一個(gè)好東西,我以前實(shí)在是太孤陋寡聞了,竟然沒有好好的去了解這樣一個(gè)被廣泛使用的模式,它給我?guī)砗芏嗟乃伎肌Mㄟ^它,使我更容易理解很多框架,資料,文章,代碼。因?yàn)椋?dāng)我一看到listener,event,observer等詞語,就知道其設(shè)計(jì)原型為“觀察者模式”。
“觀察者模式”通過定義抽象主題[Abstrsct Subject](也叫消息源[Message Source]、事件源[Evnet Source]、被觀察者),抽象觀察者[Abstract Observer](更多的叫監(jiān)聽者[listener]),及它們之間的所謂事件[Event](也叫消息[Message])來實(shí)現(xiàn)的。這里的事件其實(shí)是對(duì)被觀察者和觀察者之間傳遞信息的一個(gè)封裝,如果你覺得沒有必要封裝什么,那你用個(gè)String或int傳遞信息也是可以的。
自己再寫兩個(gè)類實(shí)現(xiàn)(Implement)上面兩個(gè)抽象類就完成了一個(gè)“觀察者模式”。
下面嘗試舉例說明,想必大家都經(jīng)常被各種垃圾短信所困擾,一般這些短信都是推薦你去訂閱一些服務(wù),如每日一警句,足球彩票預(yù)測(cè),色情資訊等等。如果你按照上面的number回了信息,就等于訂閱了,也就要付錢了。為了防止你過分反感這個(gè)例子,影響敘述,那我就找個(gè)稍微有用那么一點(diǎn)的短信訂閱服務(wù)來舉例――“天氣預(yù)報(bào)”。
提供服務(wù)的“天氣預(yù)報(bào)提供商”就是模式中的“被觀察者”,而訂閱了服務(wù)的“訂閱者”就是模式中的“觀察者”。“天氣預(yù)報(bào)服務(wù)商啊,我已經(jīng)訂閱你的天氣服務(wù)啦,我盯上你啦,你一旦有了明天的天氣情況就要及時(shí)通知我哦!”――這樣粗俗的描述或許會(huì)讓你更加容易理解,為什么天氣預(yù)報(bào)服務(wù)商被稱為“被觀察者”,而我們被稱為“觀察者”,而我們之間傳遞的信息就是天氣情況。
例子的類圖:
模擬訂閱及發(fā)送天氣預(yù)報(bào)的代碼:
1
package gs.blogExample.weatherService;
2
3
public class SimulatedEnvironment
{
4
public static void main(String[] args)
{
5
6
//定義天氣服務(wù)提供商(模式角色:被觀察者)
7
WeatherServiceProvider myProvider = new WeatherServiceProvider();
8
9
//定義兩個(gè)天氣服務(wù)訂閱者(模式角色:觀察者)
10
Housewife hw = new Housewife();
11
BusDriver bd = new BusDriver();
12
13
//他們訂閱天氣服務(wù)
14
myProvider.addServiceListener(hw);
15
myProvider.addServiceListener(bd);
16
17
System.out.println("<<Date:2005-3-12>>");
18
//天氣服務(wù)提供商得到了新的天氣信息,武漢今天9度
19
//只要天氣服務(wù)商得到了任何一個(gè)城市的新天氣,就會(huì)通知訂閱者
20
myProvider.setNewWeather(new Weather("WUHAN",9,10));
21
22
//housewife退訂天氣服務(wù)
23
myProvider.removeServiceListener(hw);
24
25
System.out.println("<<Date:2005-3-13>>");
26
//第二天,天氣供應(yīng)商又得到了新的信息,武漢今天35度
27
//武漢一天之間從9度變35度,是可信的
28
myProvider.setNewWeather(new Weather("WUHAN",35,36));
29
}
30
}
31
模擬輸出的結(jié)果如下:
<<Date:2005-3-12>>
Housewife receiving weatherInfo begins..........
Housewife said: “so cool,let's make huoguo!!”
Housewife receiving weatherInfo ended..........
BusDriver receiving weatherInfo begins..........
BusDriver said: “fine day,nothing to do.”
BusDriver receiving weatherInfo ended..........
<<Date:2005-3-13>>
BusDriver receiving weatherInfo begins..........
BusDriver said: “so hot,open air condition!!”
BusDriver receiving weatherInfo ended..........
說明:訂閱者接到天氣預(yù)報(bào)后的行為就是“隨便打印一句感受”。也就是說,例子中訂閱者Print出一句話就代表他收到天氣預(yù)報(bào)了。
從上面的輸出結(jié)果可以看到,當(dāng)Housewife在2005-3-12號(hào)退訂了天氣服務(wù)。的確,第二天天氣預(yù)報(bào)提供商就沒有再給它提供服務(wù)了。
繼續(xù)拓展這個(gè)系統(tǒng),使之提供另一種服務(wù)――“足球貼士服務(wù)”,根據(jù)“觀察者模式”的角色,添加“足球貼士提供商”(被觀察者)、“足球貼士訂閱者”(觀察者)和事件類“足球貼士事件”,另還有一個(gè)輔助描述具體足球貼士信息的類“足球貼士信息”。
繼續(xù)類圖:
模擬發(fā)送接受天氣預(yù)報(bào)和足球貼士的代碼:
1
package gs.blogExample.weatherService;
2
3
public class SimulatedEnvironment
{
4
public static void main(String[] args)
{
5
6
//定義天氣服務(wù)提供商(模式角色:被觀察者)
7
WeatherServiceProvider weatherServiceProvider = new WeatherServiceProvider();
8
9
//定義天氣服務(wù)提供商(模式角色:被觀察者)
10
SoccerTipServiceProvider soccerTipServiceProvider = new SoccerTipServiceProvider();
11
12
//定義兩個(gè)天氣服務(wù)訂閱者(模式角色:觀察者)
13
Housewife hw = new Housewife();
14
BusDriver bd = new BusDriver();
15
16
//hw、bd都訂閱天氣服務(wù),bd還訂閱了足球貼士服務(wù)
17
weatherServiceProvider.addServiceListener(hw);
18
weatherServiceProvider.addServiceListener(bd);
19
soccerTipServiceProvider.addServiceListener(bd);
20
21
System.out.println("<<Date:2005-3-12>>");
22
//天氣服務(wù)提供商得到了新的天氣信息,武漢今天9度
23
//足球貼士提供商得到了新的預(yù)測(cè)信息,MAN VS ASL 預(yù)測(cè)是1
24
weatherServiceProvider.setNewWeather(new Weather("WUHAN",9,10));
25
soccerTipServiceProvider.setNewSoccerTip(new SoccerTip("MAN VS ASL","2005-4-5","1"));
26
27
//housewife退訂天氣服務(wù)
28
weatherServiceProvider.removeServiceListener(hw);
29
30
System.out.println("<<Date:2005-3-13>>");
31
//第二天,天氣供應(yīng)商又得到了新的信息,武漢今天35度
32
weatherServiceProvider.setNewWeather(new Weather("WUHAN",35,36));
33
}
34
}
35
模擬輸出的結(jié)果如下:
<<Date:2005-3-12>>
Housewife receiving weatherInfo begins..........
Housewife said: "so cool,let's make huoguo!!"
Housewife receiving weatherInfo ended..........
BusDriver receiving weatherInfo begins..........
BusDriver said: "fine day,nothing to do."
BusDriver receiving weatherInfo ended..........
BusDriver receiving soccerTip begins..........
BusDriver said: "I am about to buy soccer lottery!!!I want to be rich!"
BusDriver receiving soccerTip ended..........
<<Date:2005-3-13>>
BusDriver receiving weatherInfo begins..........
BusDriver said: "so hot,open air condition!!"
BusDriver receiving weatherInfo ended..........
源碼下載(JBuilder Project)
3.1 天生的異步模型
1
private void sendWeatherService()
{
2
3
WeatherEvent we = new WeatherEvent(this.cityWeather);
4
5
Vector cloneListenerList = (Vector) getListenerList().clone();
6
Iterator iter = cloneListenerList.iterator();
7
while (iter.hasNext())
{
8
WeatherServiceListener listener =
9
(WeatherServiceListener) iter.next();
10
listener.WeatherArrived(we);
11
}
12
13
}
14
繼續(xù)上面的例子,天氣預(yù)報(bào)提供商為訂閱者發(fā)送天氣預(yù)報(bào)的行為被封裝在sendWeatherService方法中,而天氣預(yù)報(bào)訂閱者接受到天氣預(yù)報(bào)信息后的行為被封裝在WeatherArrived方法中。
從上面天氣預(yù)報(bào)提供商的sendWeatherService方法的代碼可以看到,提供商會(huì)遍歷所有WeatherListener,然后通過調(diào)用它們的WeatherArrived方法來向它們傳遞WeatherEvent事件。
在這里,兩個(gè)方法是在一個(gè)線程中運(yùn)行的,也就是說,對(duì)于天氣提供商來講,如果第一個(gè)listener的WeatherArrived方法沒有返回,那它就沒有辦法通知第二個(gè)listener。
具體到我們這個(gè)例子,我們是先通知Housewife再通知BusDriver的(由于遍歷順序的原因),也就是說是先執(zhí)行Housewife的WeatherArrived方法,再執(zhí)行BusDriver的WeatherArrived方法。試想,如果Housewife的WeatherArrived方法中要做的事情很多(往往家庭主婦就是如此),長(zhǎng)期得不到返回,那么BusDriver的WeatherArrived方法就長(zhǎng)期得不到執(zhí)行,也就是說BusDriver長(zhǎng)期得不到天氣消息。對(duì)于天氣這種時(shí)效性很強(qiáng)的信息,很明顯,這樣不行。是Bug!
自然的,可以運(yùn)用Java Thraed,考慮把listener們的WeatherArrived并行化,放在不同的線程里去執(zhí)行。這樣,每當(dāng)通知第一個(gè)listener都不會(huì)阻塞通知下一個(gè)listener。
的確,無論用不用觀察者模式,你加幾行java線程代碼都可以實(shí)現(xiàn)并行化。只是,“觀察者模式”無論在構(gòu)思理解層面上,還是代碼結(jié)構(gòu)上,都特別容易向并行化過渡。
為什么這面說?一句話講:“觀察者模式”中的“被觀察者角色”和“觀察者角色”本生內(nèi)在就有強(qiáng)烈的并行性特征。擁有“觀察者模式”的系統(tǒng)一般都有這個(gè)特性,而有這個(gè)特性的系統(tǒng)也一般也可以設(shè)計(jì)成“觀察者模式”。
3.2 設(shè)計(jì)并行系統(tǒng)的好方法
用Messge機(jī)制或Event機(jī)制(以“觀察者模式”原型)來設(shè)計(jì)系統(tǒng),可以更好的適應(yīng)一旦程序有多線程的需要。因?yàn)樵凇坝^察者模式”中,我們?cè)诙x角色及事件時(shí),其實(shí)它們已有并行的特征(也就是多線程的可能)。
這樣可以讓我們?cè)O(shè)計(jì)系統(tǒng)的時(shí)候,暫時(shí)不把主要的腦神經(jīng)拿去考慮線程的復(fù)雜問題,放心的先把系統(tǒng)涉及的對(duì)象設(shè)計(jì)出來。而后來一旦有了線程化的需要,可以相對(duì)方便、清晰的添加,也不會(huì)出現(xiàn)太“拗眼”的代碼群。
結(jié)論還是,有利于系統(tǒng)的清晰,維護(hù)的方便。
在Swing應(yīng)用程序的設(shè)計(jì)中,就有人這樣設(shè)計(jì),并且把它上升為一種通用方法,可參見下面的Event-Dispatch方法章節(jié)。
4 Java Swing
Swing是Java的GUI類庫(kù)。因此,Java的桌面應(yīng)用程序,也被稱為Swing應(yīng)用程序。它的優(yōu)點(diǎn)是Java的優(yōu)點(diǎn):“Write once,Run anywhere”;它的缺點(diǎn)也是Java的缺點(diǎn):“性能問題要命”。
4.1 Swing單線程模型
桌面應(yīng)用程序生命周期可以概括如下:
STEP 1:初始化界面;
STEP 2:等待你的event,主要還是鼠標(biāo)和鍵盤event;
STEP 3:event一到,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯代碼;
STEP 4:根據(jù)結(jié)果,刷新界面,用戶看到;
STEP 5:然后,又到步驟2,直到你退出。
所謂Swing單線程模型的意思就是,所有和“界面”有關(guān)系的代碼都在Swing規(guī)定的線程(稱為Swing線程)里按照順序被執(zhí)行,不可以用自己定義的線程跟“界面”有任何的接觸(例外就不贅述了)。這里的“界面”指的是Jcomponent的子類,容器、按鈕、畫布都屬于界面的范圍。這里的“和界面有關(guān)系”包括event響應(yīng)、repaint、改變屬性等等。
簡(jiǎn)單來說,就是不能用自己的線程控制這些Jcomponent對(duì)象啦。
真衰,這么干的原因還不是讓Java的桌面程序不至于太慢,結(jié)果所有(也有例外)Swing的類都被設(shè)計(jì)成“非”threadsafe的,因此也只能讓一個(gè)線程(Swing線程)訪問了。
4.2 Long-Running Event Callbacks
Swing編程中最大的一個(gè)麻煩就是處理“長(zhǎng)時(shí)間事件響應(yīng)”。對(duì)照上面生命周期的step3(event一到,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯代碼),“長(zhǎng)時(shí)間事件響應(yīng)”的意思就是Step3執(zhí)行的時(shí)間太長(zhǎng)了,方法長(zhǎng)期返回不了,以至把整個(gè)Swing線程的生命周期阻塞了,用戶看起來就像死機(jī)了。
而一般這種情況,這個(gè)“長(zhǎng)時(shí)間事件響應(yīng)”都是在Swing線程中執(zhí)行的,因此阻塞了才會(huì)這么大件事。
那就把它移出Swing線程吧,讓它自己到另外的線程里面去做。這樣即使你阻塞了,也不至于阻塞Swing線程這個(gè)事關(guān)給用戶感覺的線程。
但問題還沒有完,盡管上面的step3,也就是這個(gè)“長(zhǎng)時(shí)間事件響應(yīng)”的確對(duì)Swing線程沒有影響了,Swing線程不會(huì)被阻塞了。但是在新線程(稱為N進(jìn)程)里,程序到了step4(根據(jù)結(jié)果,刷新界面,用戶看到), 它要根據(jù)step3的結(jié)果去刷新界面,把結(jié)果顯示給用戶。而根據(jù)Swing的單線程模型,N進(jìn)程是沒有權(quán)利去接觸界面對(duì)象的,只有Swing線程才有這個(gè)權(quán)利。所以,身處N進(jìn)程的step4又必須被放回到Swing線程里去執(zhí)行。
這樣,來來回回,麻麻煩煩,把我Swing暈了,怪不得叫Swing,真是“忽悠”的意思啊。這里得到靈感,以后都用忽悠來翻譯Swing,呵呵。
5 Swing Thread Programming
為了把一些跟界面相關(guān)的代碼放回到Swing線程里去執(zhí)行,Swing類庫(kù)中提供了方法,我懶得說了。
//把任務(wù)仍回給Swing執(zhí)行,不阻塞本線程
invokeLater(Runnable GUITask)
//把任務(wù)仍回給Swing執(zhí)行,阻塞本線程
invokeAndWait(Runnable GUITask)
當(dāng)然,除了直接用這兩個(gè)方法處理長(zhǎng)時(shí)間事件響應(yīng)外,這幾天在網(wǎng)上還看到了另外兩個(gè)方法。
5.1 SwingWorker
提供了一個(gè)幫助類SwingWorker,使用方便。它把這些來來回回,麻麻煩煩的線程邏輯封裝起來。簡(jiǎn)化使用Swing多線程的復(fù)雜度。
詳細(xì)了解,請(qǐng)點(diǎn)擊相關(guān)頁面。不好訪問,也可以本站下載pdf版本。
5.2 Event-Dispatch方法
使用觀察者模式重構(gòu)系統(tǒng),優(yōu)化Swing多線程程序的設(shè)計(jì)。方法基本近似上文的“設(shè)計(jì)并行程序的好辦法”。它和SwingWork最大的區(qū)別就是,使用它是要重新設(shè)計(jì)系統(tǒng)的,會(huì)產(chǎn)生基于“觀察者模式”的角色類的。
詳細(xì)了解,請(qǐng)點(diǎn)擊相關(guān)頁面。不好訪問,也可以本站下載pdf版本。
6 結(jié)論
想借Swing Thread Programming探討一下多線程設(shè)計(jì)的一些初級(jí)問題,并推薦了一下“觀察者模式”。