架構(gòu)師之路4-設(shè)計(jì)模式和UML類(lèi)圖學(xué)習(xí)筆記

【關(guān)鍵字】設(shè)計(jì)模式的原則 設(shè)計(jì)模式 uml類(lèi)圖 一站式消化吸收學(xué)習(xí)

詞匯學(xué)習(xí)
IoC (Inversion of Control) 控制反轉(zhuǎn)
依賴(lài)注入(DI)

【引用】
【設(shè)計(jì)模式】http://www.uml.org.cn/sjms/201211023.asp
【x-a關(guān)系說(shuō)明】http://blog.csdn.net/cbk861110/article/details/9028189
【uml類(lèi)圖】http://www.cnblogs.com/alex-blog/articles/2704214.html
【設(shè)計(jì)模式大的分類(lèi)】http://blog.csdn.net/jason0539/article/details/44956775
【各種工廠模式】http://www.cnblogs.com/toutou/p/4899388.html

一、uml類(lèi)圖

UML類(lèi)圖的標(biāo)記語(yǔ)言我都忘了我記了多少遍,忘了多少遍了!只有捋出來(lái)頭緒和關(guān)聯(lián),才可能永遠(yuǎn)記住并融入你的思維方式,沒(méi)有任何聯(lián)系的東西,我們大腦是不擅長(zhǎng)處理的!
總的來(lái)說(shuō)我們的類(lèi)圖分為兩大類(lèi)
is-a和has-a
is-a 可以分為繼承和實(shí)現(xiàn),依據(jù):是否有實(shí)體函數(shù)實(shí)現(xiàn)來(lái)繼承,如果虛函數(shù),則為實(shí)現(xiàn)
has-a:包括四種關(guān)聯(lián)關(guān)系的,組合,聚合,關(guān)聯(lián)和依賴(lài),依據(jù)關(guān)系強(qiáng)弱排名

  • is a 繼承/泛化和實(shí)現(xiàn)
    圖標(biāo):空心三角箭頭,叫上實(shí)線或者虛線,父親有遺產(chǎn),我就很實(shí)在,真心實(shí)意,如果父親啥也沒(méi)有,是接口,都是虛的,只有理想讓我繼承,那么我來(lái)實(shí)現(xiàn),我的線就是虛的。
    繼承和實(shí)現(xiàn)都是is-a,但是如果父類(lèi)是接口類(lèi),那么就是實(shí)現(xiàn)了,因?yàn)榻涌陬?lèi)除了虛函數(shù),啥也沒(méi)有,沒(méi)有遺產(chǎn)的父親,怎么繼承呢?所以叫實(shí)現(xiàn),父親沒(méi)有遺產(chǎn),我來(lái)實(shí)現(xiàn)父親的遺愿,哈哈


    繼承
實(shí)現(xiàn)

組合>聚合>關(guān)聯(lián)>依賴(lài);
通過(guò)類(lèi)圖,可以發(fā)現(xiàn)圖的規(guī)律

組合
聚合
關(guān)聯(lián)
依賴(lài)

通過(guò)圖的變化關(guān)系,可以發(fā)現(xiàn),關(guān)系性越強(qiáng)的,箭頭內(nèi)容就越多,比如組合,實(shí)線,有實(shí)心菱形,關(guān)系越弱的,箭頭內(nèi)容越少,比如依賴(lài),虛線,沒(méi)有菱形。簡(jiǎn)直是大發(fā)現(xiàn)!

下面的幾個(gè)都是contain-a,has-a,relate-a,rely-a

  • contains-a 組合關(guān)系,包含關(guān)系,強(qiáng)聚合
    組合關(guān)系是局部和整體不可分開(kāi)的,腦仁和腦袋的關(guān)系,分開(kāi)了就不好了,要死人的!
    我和我的大腦也是這個(gè)關(guān)系,不能缺少局部單元;
組合,菱形是實(shí)心的,說(shuō)明這個(gè)局部實(shí)實(shí)在在的忠誠(chéng)于我,不會(huì)離開(kāi)我
  • has a 聚合關(guān)系
    是可以分割的,比如一袋子食品,袋子里裝著饅頭和包子,那么禮物袋和饅頭包子就是聚合,隨時(shí)可以分離
聚合,菱形空心
  • 關(guān)聯(lián)
    長(zhǎng)期關(guān)系,比如朋友,同事關(guān)系,都是長(zhǎng)期性的,非臨時(shí)性的
關(guān)聯(lián)-長(zhǎng)期穩(wěn)定關(guān)系
  • 依賴(lài)


    依賴(lài)-最脆弱的關(guān)系,臨時(shí)性,脆弱性

二、設(shè)計(jì)模式原則-六大原則

  • 六脈神劍,要幫我們干掉的毒性代碼
    new(對(duì)象創(chuàng)建)是有毒的
public class MovieLister {
    private MovieFinder finder;

    public MovieLister() {
        finder = new MovieFinderImpl();
    }
    
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        }
        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
    }
    ...
}

2.1、設(shè)計(jì)原則 深度理解【此小節(jié)引用知乎的一個(gè)問(wèn)答】

https://www.zhihu.com/question/19582024

那些年,空氣中仿佛還能聞到漢唐盛世的余韻,因此你決不允許自己的臉上有油光,時(shí)刻保持活力。然而,你一定曾為這些“高深術(shù)語(yǔ)”感到過(guò)困擾。也許時(shí)至今日,你仍對(duì)它們一知半解。不過(guò)就在今天,這一切都將徹底改變!我將帶領(lǐng)你以一種全新的高清視角進(jìn)入奇妙的編程世界,領(lǐng)略涵泳在這些“高深術(shù)語(yǔ)”中的活潑潑的地氣,以及翩躚于青萍之末的云水禪心。

image

·內(nèi)聚

內(nèi)聚,通俗的來(lái)講,就是自己的東西自己保管,自己的事情自己做。

經(jīng)典理論告訴我們,程序的兩大要素:一個(gè)是數(shù)據(jù)(data),一個(gè)是操作(opration)。而 PASCAL之父Nicklaus Wirth則進(jìn)一步提出了“程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法”的著名公式。雖然提法上有所差異,但是其根本內(nèi)涵卻是一致的,微妙的差別在于,“數(shù)據(jù) + 操作”是微觀的視域,“數(shù)據(jù)結(jié)構(gòu) + 算法”則是中觀的視域。而在宏觀的視域下,我認(rèn)為“程序 = 對(duì)象 + 消息”。對(duì)象是什么?對(duì)象就是保管好自己的東西,做好自己的事情的程序模塊——這就是內(nèi)聚!傳統(tǒng)的面向過(guò)程編程方法由于割裂了數(shù)據(jù)結(jié)構(gòu)和算法,使得軟件的內(nèi)聚性普遍低迷,曾一度引發(fā)了軟件危機(jī)。試想,大家都自己的東西不好好保管,自己的事情也不好好做,不引發(fā)危機(jī)才怪呢!當(dāng)然,對(duì)象的內(nèi)聚只是內(nèi)聚的一個(gè)層次,在不同的尺度下其實(shí)都有內(nèi)聚的要求,比如方法也要講內(nèi)聚,架構(gòu)也要講內(nèi)聚。

《周易·彖傳》中講“乾道變化,各正性命,保合太和,乃利貞”,就是要求每一個(gè)個(gè)體因循著各自的稟賦而努力成就各自的品性,然后各自保全,彼此和合,最終達(dá)成宇宙的完滿狀態(tài)?!墩撜Z(yǔ)·憲問(wèn)》中,子路問(wèn)君子。子曰:“修己以敬?!痹唬骸叭缢苟押??”曰:“修己以安人”,更是明確的教導(dǎo)我們要不斷提高自身的內(nèi)聚性,最大限度地減少給他人造成的麻煩,從而達(dá)到安人、安百姓、安天下的目標(biāo)。我想,成長(zhǎng)的過(guò)程就是一個(gè)不斷提升內(nèi)聚的過(guò)程?!白约旱臇|西自己保管,自己的事情自己做”,這些孩提時(shí)代的教誨,放到今天仍能讓不少“大人”臉紅不已。太多的人保管不好自己的“東西”,保管不好自己的身體,保管不好自己的婚姻,更保管不好自己如蛛絲般震顫飄蕩的狂亂的心。至于做好自己的事情,則更是惘然,甚至很多人連自己的事情是什么都搞不清楚,因此渾渾噩噩,飽食終日。內(nèi)聚,是一個(gè)值得我們好好反思的問(wèn)題。

·依賴(lài)·耦合

在面向?qū)ο缶幊讨校瑢?duì)象自身是內(nèi)聚的,是保管好自己的數(shù)據(jù),完成好自己的操作的,而對(duì)外界呈現(xiàn)出自己的狀態(tài)和行為。但是,沒(méi)有絕對(duì)的自力更生,對(duì)外開(kāi)放也是必要的!一個(gè)對(duì)象,往往需要跟其他對(duì)象打交道,既包括獲知其他對(duì)象的狀態(tài),也包括仰賴(lài)其他對(duì)象的行為,而一旦這樣的事情發(fā)生時(shí),我們便稱(chēng)該對(duì)象依賴(lài)于另一對(duì)象。只要兩個(gè)對(duì)象之間存在一方依賴(lài)一方的關(guān)系,那么我們就稱(chēng)這兩個(gè)對(duì)象之間存在耦合。 比如媽媽和baby,媽媽要隨時(shí)關(guān)注baby的睡、醒、困、哭、尿等等狀態(tài),baby則要仰賴(lài)媽媽的喂奶、哄睡、換紙尿褲等行為,從程序的意義上說(shuō),二者互相依賴(lài),因此也存在耦合。首先要說(shuō),耦合是必要的。我們來(lái)看以下這個(gè)實(shí)驗(yàn)。

【王陽(yáng)明與山中之花

View Code

由于王陽(yáng)明這個(gè)對(duì)象不依賴(lài)山花這個(gè)對(duì)象,又沒(méi)有其他的方式來(lái)獲知山花的盛開(kāi)狀態(tài),所以他要么選擇不說(shuō),要么瞎說(shuō),但不說(shuō)編譯是通不過(guò),而瞎說(shuō)作為王陽(yáng)明來(lái)講也是通不過(guò)的,所以這個(gè)系統(tǒng)是無(wú)法成立的。要想系統(tǒng)成立,必須要這樣寫(xiě):

        public bool AdmireFlowers()
        {
            return flower.IsBloomed; ; 
        }

無(wú)論這個(gè)山花對(duì)象是怎么來(lái)的,作為參數(shù)傳入還是作為屬性設(shè)置、還是在內(nèi)部構(gòu)造出來(lái),總之,王陽(yáng)明與山花之間發(fā)生了依賴(lài),二者之間產(chǎn)生了耦合。 當(dāng)然,這是一個(gè)很淺顯的問(wèn)題。有趣的是王陽(yáng)明對(duì)此事的看法:“你未看花時(shí),花與你同寂;你來(lái)看花,花于你則一時(shí)分明起來(lái)??梢?jiàn)心外無(wú)物!”王陽(yáng)明講的是對(duì)的!“心外無(wú)物”翻譯技術(shù)語(yǔ)言是這樣的:不存在耦合的兩個(gè)對(duì)象必然拿不到對(duì)方的引用!

·耦合度·解耦和

耦合的程度就是耦合度,也就是雙方依賴(lài)的程度。上文所說(shuō)的媽媽和baby就是強(qiáng)耦合。而你跟快遞小哥之間則是弱耦合。一般來(lái)說(shuō)耦合度過(guò)高并不是一件好事。就拿作為IT精英的你來(lái)說(shuō)吧,上級(jí)隨時(shí)敦促你的工作進(jìn)度,新手頻繁地需要你指導(dǎo)問(wèn)題,隔三差五還需要參加酒局飯局,然后還要天天看領(lǐng)導(dǎo)的臉色、關(guān)注老婆的心情,然后你還要關(guān)注代碼中的bug 、bug、bug,和需求的變化、變化、變化,都?jí)蚪诡^爛額了,還猝不及防的要關(guān)注眼睛、頸椎、前列腺和頭發(fā)的狀態(tài),然后你再炒個(gè)股,這些加起來(lái)大概就是個(gè)強(qiáng)耦合了。從某種意義上來(lái)說(shuō),耦合天生就與自由為敵,無(wú)論是其他對(duì)象依賴(lài)于你,還是你依賴(lài)其他對(duì)象。比如有人嗜煙、酗酒,你有多依賴(lài)它們就有多不自由;比如有人家里生了七八個(gè)娃,還有年邁的父母、岳父母,他們有多依賴(lài)你,你就有多不自由。所以老子這樣講:“五音令人耳聾,五色令人目盲,馳騁狩獵令人心發(fā)狂,難得之貨令人行妨?!北R梭也是不無(wú)悲涼的說(shuō)“人生而自由,卻又無(wú)往而不在枷鎖中”。因此,要想自由,就必須要降低耦合,而這個(gè)過(guò)程就叫做解耦和。

·依賴(lài)倒置(Dependence Inversion Principle)

解耦和最重要的原則就是依賴(lài)倒置原則:

高層模塊不應(yīng)該依賴(lài)底層模塊,他們都應(yīng)該依賴(lài)抽象。抽象不應(yīng)該依賴(lài)于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴(lài)于抽象。

《資本論》中都曾闡釋依賴(lài)倒轉(zhuǎn)原則——在商品經(jīng)濟(jì)的萌芽時(shí)期,出現(xiàn)了物物交換。假設(shè)你要買(mǎi)一個(gè)IPhone,賣(mài)IPhone的老板讓你拿一頭豬跟他換,可是你并沒(méi)有養(yǎng)豬,你只會(huì)編程。所以你找到一位養(yǎng)豬戶,說(shuō)給他做一個(gè)養(yǎng)豬的APP來(lái)?yè)Q他一頭豬,他說(shuō)換豬可以,但是得用一條金項(xiàng)鏈來(lái)?yè)Q——所以這里就出現(xiàn)了一連串的對(duì)象依賴(lài),從而造成了嚴(yán)重的耦合災(zāi)難。解決這個(gè)問(wèn)題的最好的辦法就是,買(mǎi)賣(mài)雙發(fā)都依賴(lài)于抽象——也就是貨幣——來(lái)進(jìn)行交換,這樣一來(lái)耦合度就大為降低了。

再舉一個(gè)編程中的依賴(lài)倒置的例子。我們知道,在通信中,消息的收發(fā)和消息的處理往往密不可分。就一般的通信框架而言,消息的收發(fā)通常是已經(jīng)實(shí)現(xiàn)了的,而消息的處理則是需要用戶來(lái)自定義完成的。先看一個(gè)正向依賴(lài)的例子:<u>輕量級(jí)通信引擎StriveEngine</u>。tcpServerEngine是StriveEngine.dll提供通信引擎,它發(fā)布有一個(gè)MessageReceived事件。假設(shè)我定義了一個(gè)CustomizeHandler類(lèi)來(lái)用于消息處理,那么CustomizeHandler的內(nèi)部需要預(yù)定tcpServerEngine的MessageReceived事件,因此customizeHandler依賴(lài)于tcpServerEngine,這就是一個(gè)普通的依賴(lài)關(guān)系,也就是高層模塊依賴(lài)于低層模塊。

image
image

ESFramework通信框架應(yīng)用了依賴(lài)倒轉(zhuǎn)原則。ESFramework定義了一個(gè)IcustomizeHandler接口,用戶在進(jìn)行消息處理時(shí),實(shí)現(xiàn)該接口,然后將其注入到rapidPassiveEngine客戶端通信引擎之中。

View Code

很明顯,相比于上一個(gè)例子,這里的依賴(lài)關(guān)系變成了rapidPassiveEngine依賴(lài)于customizeHandler,也就是說(shuō)依賴(lài)關(guān)系倒置了過(guò)來(lái),上層模塊不再依賴(lài)于底層模塊,而是它們共同依賴(lài)于抽象。rapidPassiveEngine依賴(lài)的是IcustomizeHandler接口類(lèi)型的參數(shù),customizeHandler同樣是以實(shí)現(xiàn)的接口的方式依賴(lài)于IcustomizeHandler——這就是一個(gè)依賴(lài)倒置的典范。

控制反轉(zhuǎn)(Inversion of Control)

  • 控制反轉(zhuǎn)跟依賴(lài)倒置是如出一轍的兩個(gè)概念,當(dāng)存在依賴(lài)倒置的時(shí)候往往也存在著控制反轉(zhuǎn)。但是控制反轉(zhuǎn)也有自己的獨(dú)特內(nèi)涵。

  • 框架將調(diào)用開(kāi)發(fā)人員的代碼,而不是其他方式。該框架實(shí)際上是一個(gè)可擴(kuò)展的結(jié)構(gòu),它為開(kāi)發(fā)人員提供了一組注入自定義代碼段的切入點(diǎn)。

引用
https://coyee.com/article/12113-three-design-patterns-that-use-inversion-of-control-sitepoint
這篇博客:主要思想就是控制反轉(zhuǎn)依靠三種方式(以來(lái)注入,觀察者模式,模板方法)實(shí)現(xiàn),然后框架調(diào)用用戶的代碼,用戶將實(shí)現(xiàn)代碼依靠接口傳遞給框架

首先我們要區(qū)分兩個(gè)角色,server 跟 Client,也就是服務(wù)方和客戶方。提供服務(wù)端的一方稱(chēng)為服務(wù)方,請(qǐng)求服務(wù)的一方稱(chēng)為客戶方。我們最熟悉的例子就是分布式應(yīng)用的C/S架構(gòu),服務(wù)端和客戶端。其實(shí)除此之外,C/S關(guān)系處處可見(jiàn)。比如在TCP/IP協(xié)議棧中,我們知道,每層協(xié)議為上一層提供服務(wù),那么這里就是一個(gè)C/S關(guān)系。當(dāng)我們使用開(kāi)發(fā)框架時(shí),開(kāi)發(fā)框架就是作為服務(wù)方,而我們自己編寫(xiě)的業(yè)務(wù)應(yīng)用就是客戶方。當(dāng)Client調(diào)用server時(shí),這個(gè)叫做一般的控制;而當(dāng)server調(diào)用Client時(shí),就是我們所說(shuō)的控制反轉(zhuǎn),同時(shí)我們也將這個(gè)調(diào)用稱(chēng)為“回調(diào)”??刂品崔D(zhuǎn)跟依賴(lài)倒置都是一種編程思想,依賴(lài)倒置著眼于調(diào)用的形式,而控制反轉(zhuǎn)則著眼于程序流程的控制權(quán)。一般來(lái)說(shuō),程序的控制權(quán)屬于server,而一旦控制權(quán)交到Client,就叫控制反轉(zhuǎn)。比如你去下館子,你是Client餐館是server。你點(diǎn)菜,餐館負(fù)責(zé)做菜,程序流程的控制權(quán)屬于server;而如果你去自助餐廳,程序流程的控制權(quán)就轉(zhuǎn)到Client了,也就是控制反轉(zhuǎn)。


image

控制反轉(zhuǎn)的思想體現(xiàn)在諸多領(lǐng)域。比如事件的發(fā)布/ 訂閱就是一種控制反轉(zhuǎn),GOF設(shè)計(jì)模式中也多處體現(xiàn)了控制反轉(zhuǎn),比如典型的模板方法模式等。而開(kāi)發(fā)框架則是控制反轉(zhuǎn)思想應(yīng)用的集中體現(xiàn)。比如之前所舉的ESFramework通信框架的例子,通信引擎回調(diào)用戶自定義的消息處理器,這就是一個(gè)控制反轉(zhuǎn)。以及ESFramework回調(diào)用戶自定義的群組關(guān)系和好友關(guān)系,回調(diào)用戶自定義的用戶管理器以管理在線用戶相關(guān)狀態(tài),回調(diào)用戶自定義的登陸驗(yàn)證處理,等等不一而足。再比如與ESFramework一脈相承的輕量級(jí)通信引擎StriveEngine,通過(guò)回調(diào)用戶自定義的通信協(xié)議來(lái)實(shí)現(xiàn)更加靈活的通信。

由此我們也可以總結(jié)出開(kāi)發(fā)框架與類(lèi)庫(kù)的區(qū)別:使用開(kāi)發(fā)框架時(shí),框架掌握程序流程的控制權(quán),而使用類(lèi)庫(kù)時(shí),則是應(yīng)用程序掌握程序流程的控制權(quán)?;蛘哒f(shuō),使用框架時(shí),程序的主循環(huán)位于框架中,而使用類(lèi)庫(kù)時(shí),程序的主循環(huán)位于應(yīng)用程序之中。框架會(huì)回調(diào)應(yīng)用程序,而類(lèi)庫(kù)則不會(huì)回調(diào)應(yīng)用程序。ESFramework和StriveEngine中最主要的對(duì)象都以engine來(lái)命名,我們也可以看出框架對(duì)于程序主循環(huán)的控制——它會(huì)為你把握方向、眼看前方、輕松駕馭!

·依賴(lài)注入(Dependency Injection)
【啊哈】大家有沒(méi)有注意到,我們的c語(yǔ)言的函數(shù)內(nèi)部就是利用形參來(lái)進(jìn)行普通的邏輯實(shí)現(xiàn)和運(yùn)算,然后實(shí)際需要用到的數(shù)據(jù)以實(shí)參傳入,依賴(lài)注入就像函數(shù)的參數(shù)一樣,把實(shí)體對(duì)象或者函數(shù)指針傳入,不改變框架的代碼,而框架中的代碼以 function定義的函數(shù)指針進(jìn)行邏輯實(shí)現(xiàn),或者以形參中的對(duì)象或者類(lèi)進(jìn)行實(shí)現(xiàn),實(shí)際使用的時(shí)候傳入外部的對(duì)象或者函數(shù)即可!道理如出一轍,很好理解!

  • 構(gòu)造函數(shù)的參數(shù)注入
public class MovieLister {
    private MovieFinder finder;

    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}
  • setter注入
public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
}
  • 接口注入
    接口注入使用接口來(lái)提供setter方法,其實(shí)現(xiàn)方式如下
public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}

class MovieLister implements InjectFinder {
    ...
    public void injectFinder(MovieFinder finder) {
      this.finder = finder;
    }
    ...
}

依賴(lài)注入與依賴(lài)倒置、控制反轉(zhuǎn)的關(guān)系仍舊是一本萬(wàn)殊。依賴(lài)注入,就其廣義而言,即是通過(guò)“注入”的方式,來(lái)獲得依賴(lài)。我們知道,A對(duì)象依賴(lài)于B對(duì)象,等價(jià)于A對(duì)象內(nèi)部存在對(duì)B對(duì)象的“調(diào)用”,而前提是A對(duì)象內(nèi)部拿到了B對(duì)象的引用。B對(duì)象的引用的來(lái)源無(wú)非有以下幾種:A對(duì)象內(nèi)部創(chuàng)建(無(wú)論是作為字段還是作為臨時(shí)變量)、構(gòu)造器注入、屬性注入、方法注入。后面三種方式統(tǒng)稱(chēng)為“依賴(lài)注入”,而第一種方式我也生造了一個(gè)名詞,稱(chēng)為“依賴(lài)內(nèi)生”,二者根本的差異即在于,我所依賴(lài)的對(duì)象的創(chuàng)建工作是否由我自己來(lái)完成。當(dāng)然,這個(gè)是廣義的依賴(lài)注入的概念,而我們一般不會(huì)這樣來(lái)使用。我們通常使用的,是依賴(lài)注入的狹義的概念。不過(guò),直接陳述其定義可能會(huì)過(guò)于詰屈聱牙,我們還是從具體的例子來(lái)看。

image

比如OMCS網(wǎng)絡(luò)語(yǔ)音視頻框架,它實(shí)現(xiàn)了多媒體設(shè)備(麥克風(fēng)、攝像頭、桌面、電子白板)的采集、編碼、網(wǎng)絡(luò)傳送、解碼、播放(或顯示)等相關(guān)的一整套流程,可以快速地開(kāi)發(fā)出視頻聊天系統(tǒng)、視頻會(huì)議系統(tǒng)、遠(yuǎn)程醫(yī)療系統(tǒng)、遠(yuǎn)程教育系統(tǒng)、網(wǎng)絡(luò)監(jiān)控系統(tǒng)等等基于網(wǎng)絡(luò)多媒體的應(yīng)用系統(tǒng)。然而,OMCS直接支持的是通用的語(yǔ)音視頻設(shè)備,而在某些系統(tǒng)中,需要使用網(wǎng)絡(luò)攝像頭或者特殊的視頻采集卡作為視頻源,或者其它的聲音采集設(shè)備作為音頻源,OMCS則提供了擴(kuò)展接口——用戶自己實(shí)現(xiàn)這個(gè)擴(kuò)展的接口,然后以“依賴(lài)注入”的方式將對(duì)象實(shí)例注入到OMCS中,從而完成對(duì)音、視頻設(shè)備的擴(kuò)展。

“依賴(lài)注入”常常用于擴(kuò)展,尤其是在開(kāi)發(fā)框架的設(shè)計(jì)中。從某種意義上來(lái)說(shuō),任何開(kāi)發(fā)框架,天生都是不完整的應(yīng)用程序。因此,一個(gè)優(yōu)秀的開(kāi)發(fā)框架,不僅要讓開(kāi)發(fā)者能夠重用這些久經(jīng)考驗(yàn)的的卓越的解決方案,也要讓開(kāi)發(fā)者能夠向框架中插入自定義的業(yè)務(wù)邏輯,從而靈活自由地適應(yīng)特定的業(yè)務(wù)場(chǎng)景的需要——也就是說(shuō)要具備良好的可擴(kuò)展性。比如上面提到的OMCS網(wǎng)絡(luò)語(yǔ)音視頻框架可應(yīng)用于音、視頻聊天系統(tǒng)、視頻會(huì)議系統(tǒng)、遠(yuǎn)程醫(yī)療系統(tǒng)、遠(yuǎn)程教育系統(tǒng)、網(wǎng)絡(luò)監(jiān)控系統(tǒng)等等基于網(wǎng)絡(luò)多媒體的應(yīng)用系統(tǒng);以及ESFramework通信框架能夠應(yīng)用于即時(shí)通訊系統(tǒng),大型多人在線游戲、在線網(wǎng)頁(yè)游戲、文件傳送系統(tǒng)、數(shù)據(jù)采集系統(tǒng)、分布式OA系統(tǒng)等任何需要分布式通信的軟件系統(tǒng)中——這種良好的擴(kuò)展性都與“依賴(lài)注入”的使用密不可分!

·面向接口編程

談到最后,“面向接口編程”已經(jīng)是呼之欲出。無(wú)論是依賴(lài)倒置、控制反轉(zhuǎn)、還是依賴(lài)注入,都已經(jīng)蘊(yùn)含著“面向接口編程”的思想。面向接口,就意味著面向抽象。作為哲學(xué)范疇而言,規(guī)定性少稱(chēng)為抽象,規(guī)定性多稱(chēng)為具體。而接口,就是程序中的一種典型的“抽象”的形式。面向抽象,就意味著面向事物的本質(zhì)規(guī)定性,擺脫感性雜多的牽絆,從而把握住“必然”——而這本身就意味著自由,因?yàn)樽杂删褪菍?duì)必然的認(rèn)識(shí)。

也許以上的這段論述太過(guò)“哲學(xué)”,但是“一本之理”與“萬(wàn)殊之理”本身就“體用不二”——總結(jié)來(lái)看,依賴(lài)倒置、控制反轉(zhuǎn)、依賴(lài)注入都圍繞著“解耦和”的問(wèn)題,而同時(shí)自始至終又都是“面向接口編程”的方法——因此,“面向接口編程”天生就是“解耦和”的好辦法。由此也印證了從“抽象”到“自由”的這一段范疇的辯證衍化。

“面向?qū)ο蟆迸c“面向接口”并非兩種不同的方法學(xué),“面向接口”其實(shí)是“面向?qū)ο蟆钡膬?nèi)在要求,是其一部分內(nèi)涵的集中表述。我們對(duì)于理想軟件的期待常被概括為“高內(nèi)聚,低耦合”,這也是整個(gè)現(xiàn)代軟件開(kāi)發(fā)方法學(xué)所追求的目標(biāo)。面向?qū)ο蠓椒▽W(xué)作為現(xiàn)代軟件開(kāi)發(fā)方法學(xué)的代表,本身就蘊(yùn)含著“高內(nèi)聚,低耦合”的思想精髓,從這個(gè)意義上來(lái)說(shuō),“面向?qū)ο蟆边@個(gè)表述更加側(cè)重于“高內(nèi)聚”,“面向接口”的表述則更加側(cè)重于“低耦合”——不過(guò)是同一事物的不同側(cè)面罷了。

除此之外,我們也能從“面向接口編程”的思想中得到“世俗”的啟迪——《論語(yǔ)》里面講,不患無(wú)位,患所以立;不患人之不己知,患其不能也——就是教導(dǎo)我們要面向“我有沒(méi)有的本事?”、“我有沒(méi)有能力?”這樣的接口,而不是面向“我有沒(méi)有搞到位子?”、“別人了不了解我?”這樣的具體。依我看,這是莫大的教誨!

2.2 六大原則詳細(xì)講解

  • 單一原則
    【】定義:
    不要存在多于一個(gè)導(dǎo)致類(lèi)變更的原因。通俗的說(shuō),即一個(gè)類(lèi)只負(fù)責(zé)一項(xiàng)職責(zé)。
    【】問(wèn)題由來(lái):
    類(lèi)T負(fù)責(zé)兩個(gè)不同的職責(zé):職責(zé)P1,職責(zé)P2。當(dāng)由于職責(zé)P1需求發(fā)生改變而需要修改類(lèi)T時(shí),有可能會(huì)導(dǎo)致原本運(yùn)行正常的職責(zé)P2功能發(fā)生故障。
  • 依賴(lài)倒置原則
    就是盡量依賴(lài)接口,不要依賴(lài)具體實(shí)現(xiàn),依賴(lài)一個(gè)很少變化的對(duì)象,如果你依賴(lài)的實(shí)現(xiàn)對(duì)象老是變化,那你豈不是也要跟著老是改動(dòng),這樣很不爽,對(duì)吧!
    【】定義:
    高層模塊不應(yīng)該依賴(lài)低層模塊,二者都應(yīng)該依賴(lài)其抽象;抽象不應(yīng)該依賴(lài)細(xì)節(jié);細(xì)節(jié)應(yīng)該依賴(lài)抽象。
    【】問(wèn)題由來(lái):
    類(lèi)A直接依賴(lài)類(lèi)B,假如要將類(lèi)A改為依賴(lài)類(lèi)C,則必須通過(guò)修改類(lèi)A的代碼來(lái)達(dá)成。這種場(chǎng)景下,類(lèi)A一般是高層模塊,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯;類(lèi)B和類(lèi)C是低層模塊,負(fù)責(zé)基本的原子操作;假如修改類(lèi)A,會(huì)給程序帶來(lái)不必要的風(fēng)險(xiǎn)。
    【】解決方案:
    將類(lèi)A修改為依賴(lài)接口I,類(lèi)B和類(lèi)C各自實(shí)現(xiàn)接口I,類(lèi)A通過(guò)接口I間接與類(lèi)B或者類(lèi)C發(fā)生聯(lián)系,則會(huì)大大降低修改類(lèi)A的幾率。
    依賴(lài)倒置原則基于這樣一個(gè)事實(shí):相對(duì)于細(xì)節(jié)的多變性,抽象的東西要穩(wěn)定的多。以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定的多。在java中,抽象指的是接口或者抽象類(lèi),細(xì)節(jié)就是具體的實(shí)現(xiàn)類(lèi),使用接口或者抽象類(lèi)的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類(lèi)去完成。
    依賴(lài)倒置原則的核心思想是面向接口編程,我們依舊用一個(gè)例子來(lái)說(shuō)明面向接口編程比相對(duì)于面向?qū)崿F(xiàn)編程好在什么地方。場(chǎng)景是這樣的,母親給孩子講故事,只要給她一本書(shū),她就可以照著書(shū)給孩子講故事了
  • 接口隔離原則
    【】定義:
    客戶端不應(yīng)該依賴(lài)它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(lài)應(yīng)該建立在最小的接口上。
    【】問(wèn)題由來(lái):
    類(lèi)A通過(guò)接口I依賴(lài)類(lèi)B,類(lèi)C通過(guò)接口I依賴(lài)類(lèi)D,如果接口I對(duì)于類(lèi)A和類(lèi)B來(lái)說(shuō)不是最小接口,則類(lèi)B和類(lèi)D必須去實(shí)現(xiàn)他們不需要的方法。
沒(méi)有遵守最小接口原則的依賴(lài)關(guān)系
遵守了接口最小原則的依賴(lài)關(guān)系

通過(guò)這兩個(gè)圖,可以很容易發(fā)現(xiàn),最小接口原則,就是提取公因數(shù)到一個(gè)接口里邊,向上面的I1,方法1都提取到了這個(gè)接口中

  • 里式替換原則
    肯定有不少人跟我剛看到這項(xiàng)原則的時(shí)候一樣,對(duì)這個(gè)原則的名字充滿疑惑。其實(shí)原因就是這項(xiàng)原則最早是在1988年,由麻省理工學(xué)院的一位姓里的女士(Barbara Liskov)提出來(lái)的。
    【】定義1:
    如果對(duì)每一個(gè)類(lèi)型為 T1的對(duì)象 o1,都有類(lèi)型為 T2 的對(duì)象o2,使得以 T1定義的所有程序 P 在所有的對(duì)象 o1 都代換成 o2 時(shí),程序 P 的行為沒(méi)有發(fā)生變化,那么類(lèi)型 T2 是類(lèi)型 T1 的子類(lèi)型。
    【】定義2:
    所有引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象。
    【】問(wèn)題由來(lái):
    有一功能P1,由類(lèi)A完成?,F(xiàn)需要將功能P1進(jìn)行擴(kuò)展,擴(kuò)展后的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類(lèi)A的子類(lèi)B來(lái)完成,則子類(lèi)B在完成新功能P2的同時(shí),有可能會(huì)導(dǎo)致原有功能P1發(fā)生故障。
    【】解決方案:
    當(dāng)使用繼承時(shí),遵循里氏替換原則。類(lèi)B繼承類(lèi)A時(shí),除添加新的方法完成新增功能P2外,盡量不要重寫(xiě)父類(lèi)A的方法,也盡量不要重載父類(lèi)A的方法。
    繼承包含這樣一層含義:
    父類(lèi)中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對(duì)于抽象方法而言),實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類(lèi)必須遵從這些契約,但是如果子類(lèi)對(duì)這些非抽象方法任意修改,就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義。
    繼承作為面向?qū)ο笕筇匦灾?,在給程序設(shè)計(jì)帶來(lái)巨大便利的同時(shí),也帶來(lái)了弊端。比如使用繼承會(huì)給程序帶來(lái)侵入性,程序的可移植性降低,增加了對(duì)象間的耦合性,如果一個(gè)類(lèi)被其他的類(lèi)所繼承,則當(dāng)這個(gè)類(lèi)需要修改時(shí),必須考慮到所有的子類(lèi),并且父類(lèi)修改后,所有涉及到子類(lèi)的功能都有可能會(huì)產(chǎn)生故障。
  • 迪米特原則
    【】定義:
    一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解。
    【】問(wèn)題由來(lái):
    類(lèi)與類(lèi)之間的關(guān)系越密切,耦合度越大,當(dāng)一個(gè)類(lèi)發(fā)生改變時(shí),對(duì)另一個(gè)類(lèi)的影響也越大。
    【】解決方案:
    盡量降低類(lèi)與類(lèi)之間的耦合。
    【】簡(jiǎn)介:
    迪米特法則又叫最少知道原則,最早是在1987年由美國(guó)Northeastern University的Ian Holland提出。通俗的來(lái)講,就是一個(gè)類(lèi)對(duì)自己依賴(lài)的類(lèi)知道的越少越好。也就是說(shuō),對(duì)于被依賴(lài)的類(lèi)來(lái)說(shuō),無(wú)論邏輯多么復(fù)雜,都盡量地的將邏輯封裝在類(lèi)的內(nèi)部,對(duì)外除了提供的public方法,不對(duì)外泄漏任何信息。迪米特法則還有一個(gè)更簡(jiǎn)單的定義:只與直接的朋友通信。首先來(lái)解釋一下什么是直接的朋友:每個(gè)對(duì)象都會(huì)與其他對(duì)象有耦合關(guān)系,只要兩個(gè)對(duì)象之間有耦合關(guān)系,我們就說(shuō)這兩個(gè)對(duì)象之間是朋友關(guān)系。耦合的方式很多,依賴(lài)、關(guān)聯(lián)、組合、聚合等。其中,我們稱(chēng)出現(xiàn)成員變量、方法參數(shù)、方法返回值中的類(lèi)為直接的朋友,而出現(xiàn)在局部變量中的類(lèi)則不是直接的朋友。也就是說(shuō),陌生的類(lèi)最好不要作為局部變量的形式出現(xiàn)在類(lèi)的內(nèi)部。
    【】看下面這個(gè)例子
    舉一個(gè)例子:有一個(gè)集團(tuán)公司,下屬單位有分公司和直屬部門(mén),現(xiàn)在要求打印出所有下屬單位的員工ID。先來(lái)看一下違反迪米特法則的設(shè)計(jì)。
//總公司員工
class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

//分公司員工
class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;f
    }
}

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //為分公司人員按順序分配一個(gè)ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
}

class CompanyManager{

    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //為總公司人員按順序分配一個(gè)ID
            emp.setId("總公司"+i);
            list.add(emp);
        }
        return list;
    }
    
    public void printAllEmployee(SubCompanyManager sub){
        List<SubEmployee> list1 = sub.getAllEmployee();
        for(SubEmployee e:list1){
            System.out.println(e.getId());
        }

        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

public class Client{
    public static void main(String[] args){
        CompanyManager e = new CompanyManager();
        e.printAllEmployee(new SubCompanyManager());
    }
} 

現(xiàn)在這個(gè)設(shè)計(jì)的主要問(wèn)題出在CompanyManager中,根據(jù)迪米特法則,只與直接的朋友發(fā)生通信,而SubEmployee類(lèi)并不是CompanyManager類(lèi)的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友),從邏輯上講總公司只與他的分公司耦合就行了,與分公司的員工并沒(méi)有任何聯(lián)系,這樣設(shè)計(jì)顯然是增加了不必要的耦合。按照迪米特法則,應(yīng)該避免類(lèi)中出現(xiàn)這樣非直接朋友關(guān)系的耦合。修改后的代碼如下:

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //為分公司人員按順序分配一個(gè)ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
    public void printEmployee(){
        List<SubEmployee> list = this.getAllEmployee();
        for(SubEmployee e:list){
            System.out.println(e.getId());
        }
    }
}

class CompanyManager{
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //為總公司人員按順序分配一個(gè)ID
            emp.setId("總公司"+i);
            list.add(emp);
        }
        return list;
    }
    
    public void printAllEmployee(SubCompanyManager sub){
        sub.printEmployee();
        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

這兩個(gè)代碼的區(qū)別,就是

打印子公司員工的信息的方法,放到了子公司類(lèi)當(dāng)中
總公司打印方法,傳入子公司類(lèi)即可

避免了總公司的打印方法操作子公司的員工信息,這就是所謂的不要和非直接朋友關(guān)系的類(lèi)進(jìn)行通信,

  • 開(kāi)閉原則-沒(méi)有實(shí)際操作層面的原則,前面的原則執(zhí)行好了自然就開(kāi)閉了
    【】定義:
    一個(gè)軟件實(shí)體如類(lèi)、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
    【】問(wèn)題由來(lái):
    在軟件的生命周期內(nèi),因?yàn)樽兓?、升?jí)和維護(hù)等原因需要對(duì)軟件原有代碼進(jìn)行修改時(shí),可能會(huì)給舊代碼中引入錯(cuò)誤,也可能會(huì)使我們不得不對(duì)整個(gè)功能進(jìn)行重構(gòu),并且需要原有代碼經(jīng)過(guò)重新測(cè)試
    【】解決方案:
    當(dāng)軟件需要變化時(shí),盡量通過(guò)擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化,而不是通過(guò)修改已有的代碼來(lái)實(shí)現(xiàn)變化
    【】理解:
    開(kāi)閉原則是面向?qū)ο笤O(shè)計(jì)中最基礎(chǔ)的設(shè)計(jì)原則,它指導(dǎo)我們?nèi)绾谓⒎€(wěn)定靈活的系統(tǒng)。開(kāi)閉原則可能是設(shè)計(jì)模式六項(xiàng)原則中定義最模糊的一個(gè)了,它只告訴我們對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,可是到底如何才能做到對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,并沒(méi)有明確的告訴我們。以前,如果有人告訴我“你進(jìn)行設(shè)計(jì)的時(shí)候一定要遵守開(kāi)閉原則”,我會(huì)覺(jué)的他什么都沒(méi)說(shuō),但貌似又什么都說(shuō)了。因?yàn)殚_(kāi)閉原則真的太虛了。

三 常見(jiàn)設(shè)計(jì)模式

把uml類(lèi)圖和設(shè)計(jì)模式的基本概念放在前面講,是因?yàn)橄旅婧芏喔拍畹念?lèi)圖說(shuō)明會(huì)用到uml類(lèi)圖知識(shí)和設(shè)計(jì)模式知識(shí),在學(xué)習(xí)六大原則和類(lèi)圖的過(guò)程中,也要一并將上面的知識(shí)進(jìn)行融合消化理解,學(xué)習(xí)每個(gè)設(shè)計(jì)模式,盡量都要理解在uml類(lèi)圖解釋他們的說(shuō)明圖和設(shè)計(jì)模式原則層面的知識(shí)!
我們很多偉大工作者為了將知識(shí)更方便的傳播,將知識(shí)進(jìn)行了分類(lèi)傳播,但是知識(shí)接收的過(guò)程中,如果要進(jìn)行消化吸收,也必須獲取到像知識(shí)傳播者一樣足夠多的信息進(jìn)行知識(shí)融合,才能夠還原作者的意圖,所以知識(shí)學(xué)習(xí)要逆?zhèn)鞑ミ^(guò)程,這個(gè)很像我們的osi 7層模型一樣。

3.1 創(chuàng)建型模式

前面講過(guò),社會(huì)化的分工越來(lái)越細(xì),自然在軟件設(shè)計(jì)方面也是如此,因此對(duì)象的創(chuàng)建和對(duì)象的使用分開(kāi)也就成為了必然趨勢(shì)。因?yàn)閷?duì)象的創(chuàng)建會(huì)消耗掉系統(tǒng)的很多資源,所以單獨(dú)對(duì)對(duì)象的創(chuàng)建進(jìn)行研究,從而能夠高效地創(chuàng)建對(duì)象就是創(chuàng)建型模式要探討的問(wèn)題。這里有6個(gè)具體的創(chuàng)建型模式可供研究,它們分別是:
簡(jiǎn)單工廠模式(Simple Factory)
工廠方法模式(Factory Method)
抽象工廠模式(Abstract Factory)
創(chuàng)建者模式(Builder)
原型模式(Prototype)
單例模式(Singleton)
說(shuō)明:嚴(yán)格來(lái)說(shuō),簡(jiǎn)單工廠模式不是GoF總結(jié)出來(lái)的23種設(shè)計(jì)模式之一

  • 工廠模式
簡(jiǎn)單工廠模式-按照標(biāo)準(zhǔn)這個(gè)不屬于23中模式中
優(yōu)缺點(diǎn)
工廠方法模式
優(yōu)缺點(diǎn)

工廠方法模式 每個(gè)產(chǎn)品需要一個(gè)工廠子類(lèi),工廠子類(lèi)太多。

抽象工廠模式
優(yōu)缺點(diǎn)

抽象工廠,最大特點(diǎn)就是,產(chǎn)品有多個(gè)等級(jí),每個(gè)等級(jí)有多個(gè)規(guī)格和參數(shù)

抽象工廠模式和工廠方法模式的區(qū)別
創(chuàng)建者模式-builder
創(chuàng)建者模式的主要模塊
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容