自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

對(duì)不起,來(lái)晚了,御姐趣講設(shè)計(jì)模式

開發(fā) 后端
你好,歡迎來(lái)到設(shè)計(jì)模式的世界,這一篇我將用一種引導(dǎo)、啟迪的思路去講述設(shè)計(jì)模式。在程序員的世界里,設(shè)計(jì)模式就相當(dāng)于武俠世界的劍招、套路。掌握了招式,你的武學(xué)修為會(huì)得到極大提升,最終達(dá)到無(wú)招勝有招的境界。

 [[337734]]

御姐力作,深入淺出,妙趣橫生,值得一看!

## 引言

你好,歡迎來(lái)到設(shè)計(jì)模式的世界,這一篇我將用一種引導(dǎo)、啟迪的思路去講述設(shè)計(jì)模式。在程序員的世界里,設(shè)計(jì)模式就相當(dāng)于武俠世界的劍招、套路。掌握了招式,你的武學(xué)修為會(huì)得到極大提升,最終達(dá)到無(wú)招勝有招的境界。

+ 首先,我會(huì)告訴大家設(shè)計(jì)模式是什么,不是什么。

+ 然后,簡(jiǎn)單介紹一下設(shè)計(jì)模式的分類,簡(jiǎn)單羅列一下各設(shè)計(jì)模式。

+ 接著,闡述面向?qū)ο笤O(shè)計(jì)一個(gè)非常重要的設(shè)計(jì)原則:**合成復(fù)用原則**,它是核心原則,提高復(fù)用一直是軟件工程師的不懈追求,它貫穿于設(shè)計(jì)模式一書。

+ 最后,從實(shí)用出發(fā),我會(huì)詳細(xì)描述兩個(gè)最經(jīng)典最常用的設(shè)計(jì)模式:?jiǎn)卫陀^察者。我不只是介紹這兩種模式的用途和實(shí)現(xiàn)方式,還會(huì)結(jié)合自己工作實(shí)踐,拋出限制與約束,提醒注意點(diǎn),以及跟其他模式的配合方式。

希望你學(xué)完這一節(jié),可以觸類旁通,在實(shí)際項(xiàng)目中用好設(shè)計(jì)模式,為社會(huì)做貢獻(xiàn)。

## 什么是設(shè)計(jì)模式

一門工程一定會(huì)有很多實(shí)踐性的經(jīng)驗(yàn)總結(jié)。就好比造大橋,人們會(huì)總結(jié)拱橋有哪些部件組成,有什么特點(diǎn),有什么適用場(chǎng)合,懸索橋又有什么部件、特點(diǎn)、使用場(chǎng)合。這些從實(shí)踐中提煉出來(lái)的建筑模式又可以指導(dǎo)新出現(xiàn)的需求,比如去設(shè)計(jì)一個(gè)某市長(zhǎng)江大橋,你會(huì)思考有哪個(gè)成熟的模式可以適用,在這個(gè)模式下,又要如何根據(jù)實(shí)際需求定制化地設(shè)計(jì)各個(gè)部件。

軟件工程也是如此。

設(shè)計(jì)模式是設(shè)計(jì)模式是軟件開發(fā)人員在軟件開發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案,是被反復(fù)使用,多數(shù)人知曉的,經(jīng)過(guò)分類編目的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。

+ 設(shè)計(jì)模式是一般問(wèn)題的解決方案。分析多種多樣的具體需求,常常會(huì)發(fā)現(xiàn)結(jié)構(gòu)上和行為上具有的共性,常常會(huì)產(chǎn)生相似的設(shè)計(jì)。設(shè)計(jì)模式是脫離了具體需求的,某類共性問(wèn)題的解決方案。

+ 設(shè)計(jì)模式是程序設(shè)計(jì)的經(jīng)驗(yàn)總結(jié)。在其適用范圍內(nèi)正確地使用設(shè)計(jì)模式通常會(huì)產(chǎn)生高質(zhì)量的設(shè)計(jì)。

+ 設(shè)計(jì)模式彌補(bǔ)了編程語(yǔ)言的缺陷。設(shè)計(jì)模式實(shí)現(xiàn)了創(chuàng)建時(shí)多態(tài)、雙重分派等在主流編程語(yǔ)言中不直接提供的功能。反過(guò)來(lái),近年來(lái)設(shè)計(jì)思想和設(shè)計(jì)模式的發(fā)展也影響了新興語(yǔ)言的語(yǔ)言規(guī)范。

+ 設(shè)計(jì)模式是軟件工程師的一套術(shù)語(yǔ)。完整地描述一個(gè)設(shè)計(jì)通常要花費(fèi)相當(dāng)?shù)钠?,通過(guò)對(duì)設(shè)計(jì)歸類,可以便于快速表達(dá)設(shè)計(jì)的特點(diǎn)。

## 設(shè)計(jì)模式不是什么

+ 不是普適原則。設(shè)計(jì)模式并不是如SOLID設(shè)計(jì)原則一樣是放之四海而皆準(zhǔn)的普適的原則。每個(gè)設(shè)計(jì)模式都有其適用場(chǎng)景,必須根據(jù)實(shí)際情況分析決定采用哪種設(shè)計(jì)模式或不使用設(shè)計(jì)模式。在一個(gè)軟件項(xiàng)目中設(shè)計(jì)模式并不是用得越多越好,符合實(shí)際需求的高質(zhì)量的獨(dú)特設(shè)計(jì)也是好設(shè)計(jì)。

+ 不是嚴(yán)格規(guī)范。設(shè)計(jì)模式是經(jīng)驗(yàn)的總結(jié),允許根據(jù)實(shí)際需要改變和改進(jìn)。采用了設(shè)計(jì)模式并不意味著類的結(jié)構(gòu)甚至命名都要與模式嚴(yán)格符合。在應(yīng)用設(shè)計(jì)模式時(shí)應(yīng)著重吸取其設(shè)計(jì)思路,根據(jù)實(shí)際需求進(jìn)行設(shè)計(jì)。尤其是很多設(shè)計(jì)模式中的名稱過(guò)于寬泛,在實(shí)際項(xiàng)目中并不適合用作類名。

+ 不是具體類庫(kù)。設(shè)計(jì)模式有助于代碼復(fù)用,但模式本身并不是可直接復(fù)用的代碼。在設(shè)計(jì)模式中擔(dān)任特定角色的并不是特定的一個(gè)類,通常需要在具體設(shè)計(jì)中結(jié)合具體需求來(lái)實(shí)現(xiàn)?,F(xiàn)代編程語(yǔ)言中的模板、泛型等語(yǔ)言特性有助于寫出更加通用的代碼,但對(duì)于很多設(shè)計(jì)模式,完全通用的代碼庫(kù)既難實(shí)現(xiàn),又難使用。

+ 不是行業(yè)解決方案。并沒(méi)有說(shuō)哪個(gè)模式特別適合互聯(lián)網(wǎng)、哪個(gè)模式專門針對(duì)自動(dòng)化。設(shè)計(jì)模式關(guān)注軟件結(jié)構(gòu)內(nèi)在的共性,而與具體的業(yè)務(wù)領(lǐng)域無(wú)關(guān)。

有工程師言必稱設(shè)計(jì)模式,生搬硬套設(shè)計(jì)模式,之后又出現(xiàn)反設(shè)計(jì)模式的思潮,認(rèn)為設(shè)計(jì)模式是騙局,無(wú)助于軟件質(zhì)量提升。我認(rèn)為,無(wú)論是神化設(shè)計(jì)模式亦或是反設(shè)計(jì)模式都是走極端,都是錯(cuò)誤的。設(shè)計(jì)模式為我們解決一些通用性的問(wèn)題提供了良好借鑒,且在大多數(shù)情況下,行之有效。設(shè)計(jì)模式并不絕對(duì)通用,在實(shí)際項(xiàng)目中如何抉擇用哪個(gè)設(shè)計(jì)模式或是不用設(shè)計(jì)模式,非??简?yàn)工程師的水平和經(jīng)驗(yàn)。

## GOF設(shè)計(jì)模式

設(shè)計(jì)模式的流行源于一本叫《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》的書,這本書的作者是4個(gè)博士,也叫GOF(Gang of Four),軟件設(shè)計(jì)模式一詞由作者從建筑設(shè)計(jì)領(lǐng)域引入計(jì)算機(jī)科學(xué)。

書中介紹了 23 種設(shè)計(jì)模式。這些模式可以分為三大類:

  • + 創(chuàng)建型模式:?jiǎn)卫?、原型、工廠方法、抽象工廠、建造者
  • + 結(jié)構(gòu)型模式:代理、適配、橋接、裝飾、外觀、享元、組合
  • + 行為型模式:模板方法、策略、命令、職責(zé)鏈、狀態(tài)、觀察者、中介者、迭代器、訪問(wèn)者、備忘錄、解釋器

## 合成復(fù)用原則

對(duì)于軟件復(fù)用來(lái)說(shuō),組合優(yōu)于繼承,在軟件復(fù)用時(shí),優(yōu)先考慮組合關(guān)系,其次才考慮繼承關(guān)系。

面向?qū)ο笤O(shè)計(jì)的特點(diǎn)之一是繼承,子類包含父類的所有屬性和方法,因此一個(gè)很自然的想法是為了復(fù)用父類的代碼而繼承。但是實(shí)踐發(fā)現(xiàn),用繼承關(guān)系來(lái)實(shí)現(xiàn)軟件的復(fù)用有很多缺點(diǎn),一般來(lái)說(shuō)更為合理的方式是,用多個(gè)對(duì)象的組合關(guān)系來(lái)實(shí)現(xiàn)復(fù)用。

+ 繼承關(guān)系是子類“是一個(gè)”父類的關(guān)系,但如果是為了復(fù)用父類的已有功能來(lái)實(shí)現(xiàn)子類的新功能,常常會(huì)違反里氏替換原則。

+ 組合關(guān)系更容易處理有多個(gè)可復(fù)用模塊的情況。多重繼承會(huì)導(dǎo)致結(jié)構(gòu)復(fù)雜不易維護(hù)。

+ 組合關(guān)系更靈活易擴(kuò)展,只要使用適當(dāng)?shù)脑O(shè)計(jì)模式,使用者和被使用者都可被修改、擴(kuò)展、替換。

+ 組合關(guān)系可以提供運(yùn)行時(shí)的靈活性??梢栽谶\(yùn)行時(shí)指定一個(gè)模塊的底層實(shí)現(xiàn),或者運(yùn)行時(shí)替換一個(gè)對(duì)象的內(nèi)部實(shí)現(xiàn)。

為了體現(xiàn)它的重要性,這里我們看一個(gè)具體的例子。

我們知道,隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。在隊(duì)列上可以執(zhí)行添加和刪除一個(gè)元素的操作,添加元素稱為入隊(duì),刪除元素稱為出隊(duì),并且元素出隊(duì)的順序與入隊(duì)的順序相同。顯然,隊(duì)列可以用雙向鏈表來(lái)實(shí)現(xiàn),那么,我們要不要把隊(duì)列設(shè)計(jì)成雙向鏈表的子類呢?

咋一看,可以讓queue私有繼承l(wèi)ist,隱藏掉list所有的方法,然后實(shí)現(xiàn)隊(duì)列的push方法調(diào)用list的push_pack方法,隊(duì)列的pop方法調(diào)用list的pop_front方法。非常簡(jiǎn)單直接。

但是,這種實(shí)現(xiàn)方式是有問(wèn)題的。到底啥問(wèn)題?一言兩語(yǔ)也講不清楚,你自己想去吧。

因此,C++和Java的標(biāo)準(zhǔn)庫(kù)都沒(méi)有采用這種繼承的方式實(shí)現(xiàn)隊(duì)列。

在C++的stl中,queue被設(shè)計(jì)成一個(gè)容器適配器。只要是是實(shí)現(xiàn)了push_back、pop_front的容器,都可以作為queue的底層容器。stl中就提供了2種可以套用queue的容器,是list和deque。list就是雙向鏈表。deque的實(shí)現(xiàn)是數(shù)組指針的數(shù)組,與list相比減少了內(nèi)存分配的次數(shù)。

在JDK中,Queue是一個(gè)interface,實(shí)現(xiàn)了Queue接口的有LinkedList、ArrayDeque、ConcurrentLinkedQueue、LinkedBlockingQueue等許多具體類。

為了體現(xiàn)它的重要性,這里我將用一個(gè)實(shí)例來(lái)加深你對(duì)它的印象。如果設(shè)計(jì)一個(gè)網(wǎng)絡(luò)組件庫(kù),HttpConnection應(yīng)該繼承TcpConnection嗎?

HttpConnection不再能夠提供符合TcpConnection的功能,不能當(dāng)作TcpConnection使用。考慮read方法,若直接暴露TcpConnection的read方法,則破壞內(nèi)部結(jié)構(gòu);若提供基于HTTP協(xié)議的read方法,又無(wú)法做到功能跟父類一致。

Http協(xié)議能夠使用不同的下層協(xié)議,例如TCPv6。繼承自TcpConnection就失去了這種擴(kuò)展性。

如果設(shè)計(jì)另一個(gè)類"HttpOverTcp6Connection",會(huì)導(dǎo)致二者有大量的重復(fù)代碼,而這些代碼恰恰是實(shí)現(xiàn)HTTP協(xié)議本身的功能,應(yīng)復(fù)用為好。

如果希望一個(gè)程序在IPv4和IPv6網(wǎng)絡(luò)下都可使用,需要做很多的工作來(lái)實(shí)現(xiàn)在運(yùn)行時(shí)(而非編譯時(shí))根據(jù)配置文件或用戶輸入選擇HttpConnection或HttpOverTcp6Connection。

繼承關(guān)系表達(dá)類的對(duì)外提供的功能,而非類的內(nèi)部實(shí)現(xiàn)。Java中HttpURLConnection繼承URLConnection,與之并列的是JarURLConnection,二者都提供了根據(jù)URL建立連接并通信的功能。

**下面以2個(gè)常用的設(shè)計(jì)模式為例,說(shuō)明它們的應(yīng)用場(chǎng)景和應(yīng)用價(jià)值,讓大家有一個(gè)比較直觀具體的感受。**

## 單例模式

單例模式是指,某個(gè)類負(fù)責(zé)創(chuàng)建自己的對(duì)象,同時(shí)確保只能創(chuàng)建單個(gè)對(duì)象。單例模式最簡(jiǎn)單的設(shè)計(jì)模式,也是最容易用錯(cuò)的設(shè)計(jì)模式。

### 如何實(shí)現(xiàn)單例模式

單例模式非常簡(jiǎn)單,這個(gè)模式中只包含一個(gè)類。實(shí)現(xiàn)單例模式的重點(diǎn)是管理單例實(shí)例的創(chuàng)建。

+ C++,可以通過(guò)static局部變量的方式,也可以通過(guò)static指針成員變量的條件創(chuàng)建方式做到(即每次GetInstance的時(shí)候判空,如果為空則new,否則直接返回)。Java可以用static指針成員變量的方式。

+ 通常為了避免使用者錯(cuò)誤創(chuàng)建多余的對(duì)象,單例的構(gòu)造函數(shù)和析構(gòu)函數(shù)聲明為私有函數(shù)。

+ 多線程環(huán)境下,創(chuàng)建單例的代碼需要謹(jǐn)慎處理并發(fā)的問(wèn)題。一般做法是雙重檢查加鎖(即每次判空的時(shí)候先判空一次,如果為空則加鎖再次判空)。C++的靜態(tài)局部變量可以保證線程安全,java要使用synchronized實(shí)現(xiàn)。

+ 多種單例,如果有依賴關(guān)系,需要仔細(xì)處理構(gòu)建順序。C++的靜態(tài)局部變量在程序首次運(yùn)行到變量聲明處時(shí)執(zhí)行其構(gòu)造函數(shù)。Java的靜態(tài)變量初始化發(fā)生在類被加載時(shí)。

### 單例模式的好處

+ 使用簡(jiǎn)單,任何需要用到類實(shí)例的地方,直接用類的GetInstance()方法就便利的獲取到實(shí)例。

+ 可以避免使用全局變量,讓開發(fā)者有更好的OOP感,且可以讓程序員更好地控制初始化順序。

+ 它隱藏了對(duì)象的構(gòu)建細(xì)節(jié),且能避免多次構(gòu)建引起的錯(cuò)誤。

### 單例模式的探討

從原則上說(shuō),一個(gè)類應(yīng)努力提供它應(yīng)有的功能,而不應(yīng)對(duì)它的使用者做出過(guò)多限制。而單例模式限制這個(gè)類的對(duì)象只存在唯一實(shí)例。因此單例模式只應(yīng)在確有必要的情況下使用:

+ 技術(shù)上必須保證此對(duì)象全局唯一,例如代表應(yīng)用本身、對(duì)象管理器、全局服務(wù)等。

+ 程序中多處依賴此對(duì)象,采用單例模式能使代碼得到極大簡(jiǎn)化,例如全局配置選項(xiàng)。

要避免根據(jù)一時(shí)的具體需求將某類設(shè)計(jì)為單例,而極大地限制了可擴(kuò)展性。例如一個(gè)選課系統(tǒng)如果把學(xué)校信息設(shè)計(jì)為單例,將來(lái)想要支持跨校選課時(shí)就比較困難。

尤其注意,一旦某個(gè)類設(shè)計(jì)為單例,就會(huì)形成在程序各處隨意地引用這個(gè)對(duì)象的一種傾向。這正是單例模式的便利之處,但如果并不希望一個(gè)類有如此廣泛的耦合關(guān)系,則應(yīng)避免將其設(shè)計(jì)為單例。

此外,由這種便利性會(huì)引發(fā)更不利的傾向。在未經(jīng)仔細(xì)設(shè)計(jì)的系統(tǒng)中,隨著需求變更和系統(tǒng)演進(jìn),單例類可能會(huì)無(wú)節(jié)制地?cái)U(kuò)展,包含各種難以歸類的數(shù)據(jù)成員和各個(gè)模塊的中轉(zhuǎn)方法。

### 替代方案

通常有以下方法可以避免使用單例模式:

+ 享元模式。例如Android SDK使用activity.getApplication() ,避免“Application.getSingleton() ”。這樣取得Application實(shí)例并不像單例模式那么方便,從而限制了Application的耦合性。而通過(guò)Activity獲取Application是符合邏輯的設(shè)計(jì),大多數(shù)真正需要用到Application的場(chǎng)合并不影響使用。

+ 靜態(tài)方法。例如Unity引擎的物體查詢接口是GameObject.Find(name) ,而不是由比如“GameObjectManager”的單例類提供。靜態(tài)方法只提供單一的功能,并且調(diào)用時(shí)的寫法比單例模式更加簡(jiǎn)潔。但須注意,只有邏輯上與某個(gè)類有緊密聯(lián)系的功能才適合作為靜態(tài)方法。靜態(tài)方法如果濫用,會(huì)導(dǎo)致軟件結(jié)構(gòu)實(shí)際上變成了面向過(guò)程的設(shè)計(jì)。

## 觀察者模式

觀察者模式,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),把這種改變通知給其他多個(gè)對(duì)象,從而影響其他對(duì)象的行為。又稱訂閱模式、事件模式等。

### 觀察者模式的組成

觀察者模式中包含兩個(gè)角色:

+ 被觀察者,它維護(hù)觀察者列表,并在自身發(fā)生改變時(shí)通知觀察者。也可稱為發(fā)布者、事件源等。

+ 觀察者,它將自身注冊(cè)到被觀察者維護(hù)的觀察者列表,并在接收到被觀察者的通知時(shí)做出響應(yīng)。觀察者也稱訂閱者。

### 如何實(shí)現(xiàn)觀察者模式

被觀察者的接口應(yīng)包含3個(gè)方法:增加觀察者、刪除觀察者、向觀察者發(fā)送通知。其中,增加觀察者、刪除觀察者通常由觀察者調(diào)用,用于表明哪些觀察者對(duì)象需要得到通知。發(fā)送通知方法通常由被觀察者調(diào)用,因此可以考慮定義為protected方法。發(fā)送通知方法應(yīng)遍歷自身的觀察者列表,逐一調(diào)用觀察者的接收通知方法。這3個(gè)方法功能較為明確,可以用抽象類、模板、泛型等技術(shù)提供通用實(shí)現(xiàn)。

觀察者的接口需要提供接收通知方法,以供被觀察者調(diào)用。不同的具體觀察者類型實(shí)現(xiàn)各自的接收通知方法,實(shí)現(xiàn)當(dāng)被觀察者發(fā)生改變時(shí),觀察者應(yīng)做出的響應(yīng)。

由于觀察者接口只有一個(gè)方法,在C#語(yǔ)言中deligate來(lái)代替,在C++中可以用std::function代替,這樣進(jìn)一步解耦了不同類型的觀察者,其不必派生自同一個(gè)公共接口。當(dāng)然,當(dāng)系統(tǒng)中的觀察者的確有所聯(lián)系時(shí),則不應(yīng)該過(guò)度追求解耦,顯式定義一個(gè)觀察者接口或抽象類可以使結(jié)構(gòu)更為清晰、嚴(yán)謹(jǐn)。

觀察者模式常常與命令模式配合使用。命令模式是,將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,使發(fā)出請(qǐng)求的責(zé)任和執(zhí)行請(qǐng)求的責(zé)任分割開。采用命令模式,將通知或事件封裝成對(duì)象,可以使觀察者和被觀察者之間進(jìn)一步解耦。例如,如果不希望在被觀察者的運(yùn)行過(guò)程中穿插執(zhí)行觀察者的函數(shù),則可以保存命令稍后執(zhí)行。

### 觀察者模式的特點(diǎn)和適用場(chǎng)景

每種設(shè)計(jì)模式都有其最適合的應(yīng)用場(chǎng)景,如果正確使用,可以幫助理清復(fù)雜的耦合關(guān)系,簡(jiǎn)化設(shè)計(jì)。但如果在不合適的場(chǎng)景中生搬硬套,則會(huì)把原本簡(jiǎn)單的事情搞復(fù)雜,并不能真正解決需求。觀察者模式也不例外,在實(shí)際項(xiàng)目中,必須具體問(wèn)題具體分析,考察需求是否符合觀察者模式的特點(diǎn),決定是否選用觀察者模式。

+ 觀察者模式適合一對(duì)多的關(guān)聯(lián)關(guān)系。一個(gè)被觀察者可以有零個(gè)或多個(gè)觀察者。當(dāng)然,一個(gè)程序中被觀察者可以有多個(gè),每個(gè)被觀察者都有自己的一對(duì)多關(guān)系,而相互之間沒(méi)有關(guān)聯(lián)。

+ 邏輯上的依賴關(guān)系是單向的。被觀察者往往可以獨(dú)立運(yùn)行,并不依賴觀察者。而觀察者的順利運(yùn)行依賴于被觀察者的推動(dòng),離開被觀察者就運(yùn)行不起來(lái)了。

+ 調(diào)用關(guān)系與邏輯關(guān)系是反向的。邏輯上被觀察者不依賴觀察者,但有事件發(fā)生時(shí)卻是被觀察者調(diào)用了觀察者的方法。

下面我們用一個(gè)例子來(lái)看如何應(yīng)用觀察者模式來(lái)解決具體的需求,以及使用觀察者模式帶來(lái)的好處。

我們假設(shè)需求是這樣:某個(gè)應(yīng)用程序中有多處要用到定時(shí)執(zhí)行的功能,就是到一個(gè)固定的時(shí)間需要執(zhí)行一個(gè)特定的函數(shù)。很自然,多處要用到的功能應(yīng)該提煉出來(lái)作為一個(gè)子模塊。但另一方面,我們又不希望這個(gè)定時(shí)模塊與每一個(gè)用到了定時(shí)功能的其他模塊都有很強(qiáng)的耦合。

觀察者模式可以幫助我們?cè)O(shè)計(jì)定時(shí)模塊,既能服用,又有低耦合性。這里我們的示例實(shí)現(xiàn)如下。為了突出展示觀察者模式,我對(duì)需求做了一定簡(jiǎn)化,我們的定時(shí)模塊固定在每天上午9點(diǎn)觸發(fā),不支持自定義時(shí)間。

+ [C++語(yǔ)言實(shí)現(xiàn)AlarmClock](AlarmClock.c++)

  1. #include <iostream> 
  2. #include <list> 
  3.  
  4. //簡(jiǎn)單鬧鐘,每天早上9點(diǎn)響 
  5. class AlarmClock { 
  6.     public
  7.     class Alarm { 
  8.     public
  9.         virtual ~Alarm() {} 
  10.         virtual void onClockAlarmed() = 0; 
  11.     }; 
  12.      
  13.     private: 
  14.     static const int TimeZone = 8; // 北京時(shí)間東8區(qū) 
  15.     static const int AlarmHour = 9; 
  16.      
  17.     std::list<Alarm*> alarms; 
  18.     time_t tomorrow; 
  19.      
  20.     public
  21.      
  22.     AlarmClock() { 
  23.         //將tomorrow設(shè)置為明天9點(diǎn)鐘 
  24.         time_t now = time(0); 
  25.         tomorrow = now - now % 86400 - TimeZone * 3600 + AlarmHour * 3600; 
  26.         if (tomorrow < now) 
  27.             tomorrow += 86400; 
  28.     } 
  29.      
  30.     AlarmClock(AlarmClock&) = delete
  31.  
  32.     void setAlarm(Alarm* alarm) { 
  33.         alarms.push_back(alarm); 
  34.     } 
  35.      
  36.     void unsetAlarm(Alarm* alarm) { 
  37.         alarms.remove(alarm); 
  38.     } 
  39.      
  40.     void advance() { 
  41.         tomorrow += 86400; 
  42.         for (auto alarm : alarms) { 
  43.             alarm->onClockAlarmed(); 
  44.         } 
  45.     } 
  46.      
  47.     void update(time_t now) { 
  48.         while (now >= tomorrow) { 
  49.             advance(); 
  50.         } 
  51.     } 
  52. }; 
  53.  
  54. // 資深程序員張三 
  55. class TestZhangSan : public AlarmClock::Alarm { 
  56.     public
  57.     ~TestZhangSan() {} 
  58.     TestZhangSan(AlarmClock& clock) { 
  59.         clock.setAlarm(this); 
  60.     } 
  61.      
  62.     // 開始了996的一天 
  63.     void onClockAlarmed() { 
  64.         std::cout << "Zhang San is going to work..." << std::endl; 
  65.     } 
  66. }; 
  67.  
  68. // 隔壁上夜班的王叔叔 
  69. class TestLaoWang : public AlarmClock::Alarm { 
  70.     public
  71.     ~TestLaoWang(){} 
  72.     TestLaoWang(AlarmClock& clock) { 
  73.         clock.setAlarm(this); 
  74.     } 
  75.      
  76.     // 下班回家睡覺(jué) 
  77.     void onClockAlarmed() { 
  78.         std::cout << "Lao Wang is going to bed..." << std::endl; 
  79.     } 
  80. }; 
  81.  
  82. int main(int argc, char **argv) 
  83.     AlarmClock clock; 
  84.     TestZhangSan zhang(clock); 
  85.     TestLaoWang wang(clock); 
  86.     time_t now = time(0); 
  87.     now -= now % 3600; 
  88.     for (int i = 0; i < 24; i++) { 
  89.         std::cout << "Now:" << ctime(&now); 
  90.         clock.update(now); 
  91.         now += 3600; 
  92.     } 
  93.     return 0; 

+ [Java語(yǔ)言實(shí)現(xiàn)AlarmClock](AlarmClock.java)

  1. import java.util.Calendar; 
  2. import java.util.List; 
  3. import java.util.LinkedList; 
  4.  
  5. //簡(jiǎn)單鬧鐘,每天早上9點(diǎn)響 
  6. public class AlarmClock { 
  7.     public static interface Alarm { 
  8.         void onClockAlarmed(); 
  9.     } 
  10.  
  11.     private static final int AlarmHour = 9; 
  12.      
  13.     private final List<Alarm*> alarms = new LinkedList<>(); 
  14.     private Calendar tomorrow; 
  15.      
  16.     public AlarmClock() { 
  17.         //將tomorrow設(shè)置為明天9點(diǎn)鐘 
  18.         tomorrow = Calendar.getInstance(); 
  19.         boolean addDay = tomorrow.get(Calendar.HOUR_OF_DAY) >= AlarmHour; 
  20.         tomorrow.set(Calendar.HOUR_OF_DAY, AlarmHour); 
  21.         tomorrow.set(Calendar.MINUTE, 0); 
  22.         tomorrow.set(Calendar.SECOND, 0); 
  23.         tomorrow.set(Calendar.MILLISECOND, 0); 
  24.         if (addDay) { 
  25.             tomorrow.add(Calendar.DAY_OF_MONTH, 1); 
  26.         } 
  27.     } 
  28.  
  29.     public void setAlarm(Alarm alarm) { 
  30.         alarms.add(alarm); 
  31.     } 
  32.      
  33.     public void unsetAlarm(Alarm alarm) { 
  34.         alarms.remove(alarm); 
  35.     } 
  36.      
  37.     public void advance() { 
  38.         tomorrow += 86400; 
  39.         for (Alarm alarm : alarms) { 
  40.             alarm.onClockAlarmed(); 
  41.         } 
  42.     } 
  43.      
  44.     public void update(Calendar now) { 
  45.         while (now >= tomorrow) { 
  46.             advance(); 
  47.         } 
  48.     } 
  49.  
  50.     // 資深程序員張三 
  51.     private class TestZhangSan : public Alarm { 
  52.         public
  53.         TestZhangSan(AlarmClock& clock) { 
  54.             clock.setAlarm(this); 
  55.         } 
  56.          
  57.         // 開始了996的一天 
  58.         public void onClockAlarmed() { 
  59.             System.out.println("Zhang San is going to work..."); 
  60.         } 
  61.     } 
  62.  
  63.     // 隔壁上夜班的老王 
  64.     private class TestLaoWang : public Alarm { 
  65.         public TestLaoWang(AlarmClock& clock) { 
  66.             clock.setAlarm(this); 
  67.         } 
  68.          
  69.        // 下班回家睡覺(jué) 
  70.         public void onClockAlarmed() { 
  71.             System.out.println("Lao Wang is going to bed..."); 
  72.         } 
  73.     } 
  74.  
  75.     public static void main(String []args){ 
  76.         AlarmClock clock = new AlarmClock(); 
  77.         TestZhangSan zhang = new TestZhangSan(clock); 
  78.         TestLaoWang wang = new TestLaoWang(clock); 
  79.         Calendar now = Calendar.getInstance(); 
  80.         now.set(Calendar.MINUTE, 0); 
  81.         now.set(Calendar.SECOND, 0); 
  82.         now.set(Calendar.MILLISECOND, 0); 
  83.         //假裝時(shí)間經(jīng)過(guò)了24小時(shí) 
  84.         for (int i = 0; i < 24; i++) { 
  85.             System.out.println("Now:" + now.getTime()); 
  86.             clock.update(now); 
  87.             now.add(Calendar.HOUR_OF_DAY, 1); 
  88.         } 
  89.     } 

在這個(gè)例子中,AlarmClock類是被觀察者,Alarm接口及其具體子類是觀察者。按照觀察者模式,被觀察者AlarmClock維護(hù)了它的觀察者的列表。當(dāng)時(shí)間進(jìn)行到新一天的早晨,AlarmClock的狀態(tài)發(fā)生變化,也就是產(chǎn)生了一個(gè)事件,這時(shí)AlarmClock調(diào)用每個(gè)Alarm的方法。這樣,Alarm的具體子類對(duì)象,即每個(gè)希望定時(shí)執(zhí)行的模塊,就能夠在正確的時(shí)間得到執(zhí)行。

由于采用了觀察者模式,AlarmClock與其它模塊之間只通過(guò)Alarm接口交互,AlarmClock只引用Alarm,而不需要關(guān)心每個(gè)Alarm到底是哪個(gè)具體類,也不關(guān)心調(diào)用Alarm后究竟會(huì)執(zhí)行哪些操作。如果Alarm的具體子類需要修改,我們并不需要修改AlarmClock類。如果有新的模塊需要用到定時(shí)功能,只需要讓新模塊實(shí)現(xiàn)Alarm接口即可。這就是觀察者模式降低耦合性的作用。

因?yàn)檫@個(gè)例子中被觀察者只有一個(gè),因此被觀察者的抽象接口被省略了。并且我們沒(méi)有使用Observer、Subject等非常寬泛的名字,而是結(jié)合實(shí)際情況,觀察目標(biāo)就是具體類AlarmClock類,觀察者被稱為Alarm。這樣使得整個(gè)設(shè)計(jì)非常自然,沒(méi)有生搬硬套設(shè)計(jì)模式的痕跡,哪怕是沒(méi)有學(xué)過(guò)設(shè)計(jì)模式的人也能夠看懂。這就是在具體應(yīng)用設(shè)計(jì)模式時(shí)常常應(yīng)該做的剪裁和調(diào)整。

需要指出,這個(gè)例子是為了能夠清晰演示觀察者模式而專門假設(shè)的場(chǎng)景。你可以嘗試把例子進(jìn)行擴(kuò)展。如果希望支持為每個(gè)Alarm指定不同的執(zhí)行時(shí)間,應(yīng)如何設(shè)計(jì)?如果張三多件事情需要分別定時(shí)執(zhí)行,又應(yīng)如何設(shè)計(jì)?

在實(shí)際項(xiàng)目中,業(yè)務(wù)需求一定會(huì)更為復(fù)雜,工程師需要在復(fù)雜需求中識(shí)別出在哪里使用哪種設(shè)計(jì)模式能夠帶來(lái)好處,這是需要鍛煉提升的能力。實(shí)際項(xiàng)目的設(shè)計(jì)也會(huì)根據(jù)需求做出更多的調(diào)整,多一些類或少一些類,常??雌饋?lái)跟最初學(xué)習(xí)設(shè)計(jì)模式時(shí)看到的很不一樣。因此學(xué)習(xí)設(shè)計(jì)模式重在掌握思想,不能生搬硬套。無(wú)招勝有招。

## 總結(jié)

短短一篇文章,想要講清設(shè)計(jì)模式的所有內(nèi)容幾乎是不可能完成的任務(wù),所以我沒(méi)有逐一講解,而是結(jié)合我自己工作中遇到過(guò)的問(wèn)題,來(lái)帶你重新認(rèn)識(shí)設(shè)計(jì)模式,為你樹立它的重要性的觀念,避免陷入細(xì)節(jié)泥潭,歡樂(lè)的時(shí)間過(guò)太快,又是時(shí)候說(shuō)拜拜,最后,恭喜大家,你已經(jīng)掌握了設(shè)計(jì)模式,去干一番對(duì)人類有益的事業(yè)吧。

本篇由御姐供稿,版權(quán)和解釋權(quán)歸御姐所有,文章內(nèi)容代表御姐意見,本農(nóng)夫自媒體對(duì)文章觀點(diǎn)不持立場(chǎng)。

本文轉(zhuǎn)載自微信公眾號(hào)「碼磚雜役」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼磚雜役公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 碼磚雜役
相關(guān)推薦

2021-01-31 21:47:06

Svpwm版本IQMATH

2020-02-25 09:43:13

區(qū)塊鏈blockchain疫情

2015-02-28 14:05:08

FDD-LTETD-LTE

2012-07-03 14:18:31

2021-03-04 08:06:15

ZooKeeper集群代碼

2017-05-29 21:46:06

數(shù)博會(huì)馬化騰驚喜

2013-10-15 13:29:50

設(shè)計(jì)

2017-03-30 09:34:17

開發(fā)文檔功能

2021-02-01 10:01:58

設(shè)計(jì)模式 Java單例模式

2023-01-09 07:50:29

開源開發(fā)者項(xiàng)目

2023-12-14 17:31:10

SQL表格模型功能

2023-11-02 21:11:11

JavaScript設(shè)計(jì)模式

2022-01-12 13:33:25

工廠模式設(shè)計(jì)

2023-05-04 08:47:31

命令模式抽象接口

2013-11-26 16:09:34

Android設(shè)計(jì)模式

2020-10-23 09:40:26

設(shè)計(jì)模式

2020-11-03 13:05:18

命令模式

2020-11-04 08:54:54

狀態(tài)模式

2023-04-10 09:20:13

設(shè)計(jì)模式訪客模式

2020-08-21 07:23:50

工廠模式設(shè)計(jì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)