【關鍵字】設計模式的原則 設計模式 uml類圖 一站式消化吸收學習
詞匯學習
IoC (Inversion of Control) 控制反轉
依賴注入(DI)
【引用】
【設計模式】http://www.uml.org.cn/sjms/201211023.asp
【x-a關系說明】http://blog.csdn.net/cbk861110/article/details/9028189
【uml類圖】http://www.cnblogs.com/alex-blog/articles/2704214.html
【設計模式大的分類】http://blog.csdn.net/jason0539/article/details/44956775
【各種工廠模式】http://www.cnblogs.com/toutou/p/4899388.html
一、uml類圖
UML類圖的標記語言我都忘了我記了多少遍,忘了多少遍了!只有捋出來頭緒和關聯,才可能永遠記住并融入你的思維方式,沒有任何聯系的東西,我們大腦是不擅長處理的!
總的來說我們的類圖分為兩大類
is-a和has-a
is-a 可以分為繼承和實現,依據:是否有實體函數實現來繼承,如果虛函數,則為實現
has-a:包括四種關聯關系的,組合,聚合,關聯和依賴,依據關系強弱排名
-
is a 繼承/泛化和實現
圖標:空心三角箭頭,叫上實線或者虛線,父親有遺產,我就很實在,真心實意,如果父親啥也沒有,是接口,都是虛的,只有理想讓我繼承,那么我來實現,我的線就是虛的。
繼承和實現都是is-a,但是如果父類是接口類,那么就是實現了,因為接口類除了虛函數,啥也沒有,沒有遺產的父親,怎么繼承呢?所以叫實現,父親沒有遺產,我來實現父親的遺愿,哈哈
繼承
組合>聚合>關聯>依賴;
通過類圖,可以發現圖的規律
通過圖的變化關系,可以發現,關系性越強的,箭頭內容就越多,比如組合,實線,有實心菱形,關系越弱的,箭頭內容越少,比如依賴,虛線,沒有菱形。簡直是大發現!
下面的幾個都是contain-a,has-a,relate-a,rely-a
- contains-a 組合關系,包含關系,強聚合
組合關系是局部和整體不可分開的,腦仁和腦袋的關系,分開了就不好了,要死人的!
我和我的大腦也是這個關系,不能缺少局部單元;
- has a 聚合關系
是可以分割的,比如一袋子食品,袋子里裝著饅頭和包子,那么禮物袋和饅頭包子就是聚合,隨時可以分離
- 關聯
長期關系,比如朋友,同事關系,都是長期性的,非臨時性的
-
依賴
依賴-最脆弱的關系,臨時性,脆弱性
二、設計模式原則-六大原則
- 六脈神劍,要幫我們干掉的毒性代碼
new(對象創建)是有毒的
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、設計原則 深度理解【此小節引用知乎的一個問答】
https://www.zhihu.com/question/19582024
那些年,空氣中仿佛還能聞到漢唐盛世的余韻,因此你決不允許自己的臉上有油光,時刻保持活力。然而,你一定曾為這些“高深術語”感到過困擾。也許時至今日,你仍對它們一知半解。不過就在今天,這一切都將徹底改變!我將帶領你以一種全新的高清視角進入奇妙的編程世界,領略涵泳在這些“高深術語”中的活潑潑的地氣,以及翩躚于青萍之末的云水禪心。
·內聚
內聚,通俗的來講,就是自己的東西自己保管,自己的事情自己做。
經典理論告訴我們,程序的兩大要素:一個是數據(data),一個是操作(opration)。而 PASCAL之父Nicklaus Wirth則進一步提出了“程序 = 數據結構 + 算法”的著名公式。雖然提法上有所差異,但是其根本內涵卻是一致的,微妙的差別在于,“數據 + 操作”是微觀的視域,“數據結構 + 算法”則是中觀的視域。而在宏觀的視域下,我認為“程序 = 對象 + 消息”。對象是什么?對象就是保管好自己的東西,做好自己的事情的程序模塊——這就是內聚!傳統的面向過程編程方法由于割裂了數據結構和算法,使得軟件的內聚性普遍低迷,曾一度引發了軟件危機。試想,大家都自己的東西不好好保管,自己的事情也不好好做,不引發危機才怪呢!當然,對象的內聚只是內聚的一個層次,在不同的尺度下其實都有內聚的要求,比如方法也要講內聚,架構也要講內聚。
《周易·彖傳》中講“乾道變化,各正性命,保合太和,乃利貞”,就是要求每一個個體因循著各自的稟賦而努力成就各自的品性,然后各自保全,彼此和合,最終達成宇宙的完滿狀態。《論語·憲問》中,子路問君子。子曰:“修己以敬?!痹唬骸叭缢苟押酰俊痹唬骸靶藜阂园踩恕保敲鞔_的教導我們要不斷提高自身的內聚性,最大限度地減少給他人造成的麻煩,從而達到安人、安百姓、安天下的目標。我想,成長的過程就是一個不斷提升內聚的過程?!白约旱臇|西自己保管,自己的事情自己做”,這些孩提時代的教誨,放到今天仍能讓不少“大人”臉紅不已。太多的人保管不好自己的“東西”,保管不好自己的身體,保管不好自己的婚姻,更保管不好自己如蛛絲般震顫飄蕩的狂亂的心。至于做好自己的事情,則更是惘然,甚至很多人連自己的事情是什么都搞不清楚,因此渾渾噩噩,飽食終日。內聚,是一個值得我們好好反思的問題。
·依賴·耦合
在面向對象編程中,對象自身是內聚的,是保管好自己的數據,完成好自己的操作的,而對外界呈現出自己的狀態和行為。但是,沒有絕對的自力更生,對外開放也是必要的!一個對象,往往需要跟其他對象打交道,既包括獲知其他對象的狀態,也包括仰賴其他對象的行為,而一旦這樣的事情發生時,我們便稱該對象依賴于另一對象。只要兩個對象之間存在一方依賴一方的關系,那么我們就稱這兩個對象之間存在耦合。 比如媽媽和baby,媽媽要隨時關注baby的睡、醒、困、哭、尿等等狀態,baby則要仰賴媽媽的喂奶、哄睡、換紙尿褲等行為,從程序的意義上說,二者互相依賴,因此也存在耦合。首先要說,耦合是必要的。我們來看以下這個實驗。
【王陽明與山中之花】
View Code
由于王陽明這個對象不依賴山花這個對象,又沒有其他的方式來獲知山花的盛開狀態,所以他要么選擇不說,要么瞎說,但不說編譯是通不過,而瞎說作為王陽明來講也是通不過的,所以這個系統是無法成立的。要想系統成立,必須要這樣寫:
public bool AdmireFlowers()
{
return flower.IsBloomed; ;
}
無論這個山花對象是怎么來的,作為參數傳入還是作為屬性設置、還是在內部構造出來,總之,王陽明與山花之間發生了依賴,二者之間產生了耦合。 當然,這是一個很淺顯的問題。有趣的是王陽明對此事的看法:“你未看花時,花與你同寂;你來看花,花于你則一時分明起來。可見心外無物!”王陽明講的是對的!“心外無物”翻譯技術語言是這樣的:不存在耦合的兩個對象必然拿不到對方的引用!
·耦合度·解耦和
耦合的程度就是耦合度,也就是雙方依賴的程度。上文所說的媽媽和baby就是強耦合。而你跟快遞小哥之間則是弱耦合。一般來說耦合度過高并不是一件好事。就拿作為IT精英的你來說吧,上級隨時敦促你的工作進度,新手頻繁地需要你指導問題,隔三差五還需要參加酒局飯局,然后還要天天看領導的臉色、關注老婆的心情,然后你還要關注代碼中的bug 、bug、bug,和需求的變化、變化、變化,都夠焦頭爛額了,還猝不及防的要關注眼睛、頸椎、前列腺和頭發的狀態,然后你再炒個股,這些加起來大概就是個強耦合了。從某種意義上來說,耦合天生就與自由為敵,無論是其他對象依賴于你,還是你依賴其他對象。比如有人嗜煙、酗酒,你有多依賴它們就有多不自由;比如有人家里生了七八個娃,還有年邁的父母、岳父母,他們有多依賴你,你就有多不自由。所以老子這樣講:“五音令人耳聾,五色令人目盲,馳騁狩獵令人心發狂,難得之貨令人行妨?!北R梭也是不無悲涼的說“人生而自由,卻又無往而不在枷鎖中”。因此,要想自由,就必須要降低耦合,而這個過程就叫做解耦和。
·依賴倒置(Dependence Inversion Principle)
解耦和最重要的原則就是依賴倒置原則:
高層模塊不應該依賴底層模塊,他們都應該依賴抽象。抽象不應該依賴于細節,細節應該依賴于抽象。
《資本論》中都曾闡釋依賴倒轉原則——在商品經濟的萌芽時期,出現了物物交換。假設你要買一個IPhone,賣IPhone的老板讓你拿一頭豬跟他換,可是你并沒有養豬,你只會編程。所以你找到一位養豬戶,說給他做一個養豬的APP來換他一頭豬,他說換豬可以,但是得用一條金項鏈來換——所以這里就出現了一連串的對象依賴,從而造成了嚴重的耦合災難。解決這個問題的最好的辦法就是,買賣雙發都依賴于抽象——也就是貨幣——來進行交換,這樣一來耦合度就大為降低了。
再舉一個編程中的依賴倒置的例子。我們知道,在通信中,消息的收發和消息的處理往往密不可分。就一般的通信框架而言,消息的收發通常是已經實現了的,而消息的處理則是需要用戶來自定義完成的。先看一個正向依賴的例子:<u>輕量級通信引擎StriveEngine</u>。tcpServerEngine是StriveEngine.dll提供通信引擎,它發布有一個MessageReceived事件。假設我定義了一個CustomizeHandler類來用于消息處理,那么CustomizeHandler的內部需要預定tcpServerEngine的MessageReceived事件,因此customizeHandler依賴于tcpServerEngine,這就是一個普通的依賴關系,也就是高層模塊依賴于低層模塊。
而ESFramework通信框架則應用了依賴倒轉原則。ESFramework定義了一個IcustomizeHandler接口,用戶在進行消息處理時,實現該接口,然后將其注入到rapidPassiveEngine客戶端通信引擎之中。
View Code
很明顯,相比于上一個例子,這里的依賴關系變成了rapidPassiveEngine依賴于customizeHandler,也就是說依賴關系倒置了過來,上層模塊不再依賴于底層模塊,而是它們共同依賴于抽象。rapidPassiveEngine依賴的是IcustomizeHandler接口類型的參數,customizeHandler同樣是以實現的接口的方式依賴于IcustomizeHandler——這就是一個依賴倒置的典范。
控制反轉(Inversion of Control)
控制反轉跟依賴倒置是如出一轍的兩個概念,當存在依賴倒置的時候往往也存在著控制反轉。但是控制反轉也有自己的獨特內涵。
框架將調用開發人員的代碼,而不是其他方式。該框架實際上是一個可擴展的結構,它為開發人員提供了一組注入自定義代碼段的切入點。
引用
https://coyee.com/article/12113-three-design-patterns-that-use-inversion-of-control-sitepoint
這篇博客:主要思想就是控制反轉依靠三種方式(以來注入,觀察者模式,模板方法)實現,然后框架調用用戶的代碼,用戶將實現代碼依靠接口傳遞給框架
首先我們要區分兩個角色,server 跟 Client,也就是服務方和客戶方。提供服務端的一方稱為服務方,請求服務的一方稱為客戶方。我們最熟悉的例子就是分布式應用的C/S架構,服務端和客戶端。其實除此之外,C/S關系處處可見。比如在TCP/IP協議棧中,我們知道,每層協議為上一層提供服務,那么這里就是一個C/S關系。當我們使用開發框架時,開發框架就是作為服務方,而我們自己編寫的業務應用就是客戶方。當Client調用server時,這個叫做一般的控制;而當server調用Client時,就是我們所說的控制反轉,同時我們也將這個調用稱為“回調”??刂品崔D跟依賴倒置都是一種編程思想,依賴倒置著眼于調用的形式,而控制反轉則著眼于程序流程的控制權。一般來說,程序的控制權屬于server,而一旦控制權交到Client,就叫控制反轉。比如你去下館子,你是Client餐館是server。你點菜,餐館負責做菜,程序流程的控制權屬于server;而如果你去自助餐廳,程序流程的控制權就轉到Client了,也就是控制反轉。
控制反轉的思想體現在諸多領域。比如事件的發布/ 訂閱就是一種控制反轉,GOF設計模式中也多處體現了控制反轉,比如典型的模板方法模式等。而開發框架則是控制反轉思想應用的集中體現。比如之前所舉的ESFramework通信框架的例子,通信引擎回調用戶自定義的消息處理器,這就是一個控制反轉。以及ESFramework回調用戶自定義的群組關系和好友關系,回調用戶自定義的用戶管理器以管理在線用戶相關狀態,回調用戶自定義的登陸驗證處理,等等不一而足。再比如與ESFramework一脈相承的輕量級通信引擎StriveEngine,通過回調用戶自定義的通信協議來實現更加靈活的通信。
由此我們也可以總結出開發框架與類庫的區別:使用開發框架時,框架掌握程序流程的控制權,而使用類庫時,則是應用程序掌握程序流程的控制權。或者說,使用框架時,程序的主循環位于框架中,而使用類庫時,程序的主循環位于應用程序之中。框架會回調應用程序,而類庫則不會回調應用程序。ESFramework和StriveEngine中最主要的對象都以engine來命名,我們也可以看出框架對于程序主循環的控制——它會為你把握方向、眼看前方、輕松駕馭!
·依賴注入(Dependency Injection)
【啊哈】大家有沒有注意到,我們的c語言的函數內部就是利用形參來進行普通的邏輯實現和運算,然后實際需要用到的數據以實參傳入,依賴注入就像函數的參數一樣,把實體對象或者函數指針傳入,不改變框架的代碼,而框架中的代碼以 function定義的函數指針進行邏輯實現,或者以形參中的對象或者類進行實現,實際使用的時候傳入外部的對象或者函數即可!道理如出一轍,很好理解!
- 構造函數的參數注入
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;
}
}
- 接口注入
接口注入使用接口來提供setter方法,其實現方式如下
public interface InjectFinder {
void injectFinder(MovieFinder finder);
}
class MovieLister implements InjectFinder {
...
public void injectFinder(MovieFinder finder) {
this.finder = finder;
}
...
}
依賴注入與依賴倒置、控制反轉的關系仍舊是一本萬殊。依賴注入,就其廣義而言,即是通過“注入”的方式,來獲得依賴。我們知道,A對象依賴于B對象,等價于A對象內部存在對B對象的“調用”,而前提是A對象內部拿到了B對象的引用。B對象的引用的來源無非有以下幾種:A對象內部創建(無論是作為字段還是作為臨時變量)、構造器注入、屬性注入、方法注入。后面三種方式統稱為“依賴注入”,而第一種方式我也生造了一個名詞,稱為“依賴內生”,二者根本的差異即在于,我所依賴的對象的創建工作是否由我自己來完成。當然,這個是廣義的依賴注入的概念,而我們一般不會這樣來使用。我們通常使用的,是依賴注入的狹義的概念。不過,直接陳述其定義可能會過于詰屈聱牙,我們還是從具體的例子來看。
比如OMCS網絡語音視頻框架,它實現了多媒體設備(麥克風、攝像頭、桌面、電子白板)的采集、編碼、網絡傳送、解碼、播放(或顯示)等相關的一整套流程,可以快速地開發出視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網絡監控系統等等基于網絡多媒體的應用系統。然而,OMCS直接支持的是通用的語音視頻設備,而在某些系統中,需要使用網絡攝像頭或者特殊的視頻采集卡作為視頻源,或者其它的聲音采集設備作為音頻源,OMCS則提供了擴展接口——用戶自己實現這個擴展的接口,然后以“依賴注入”的方式將對象實例注入到OMCS中,從而完成對音、視頻設備的擴展。
“依賴注入”常常用于擴展,尤其是在開發框架的設計中。從某種意義上來說,任何開發框架,天生都是不完整的應用程序。因此,一個優秀的開發框架,不僅要讓開發者能夠重用這些久經考驗的的卓越的解決方案,也要讓開發者能夠向框架中插入自定義的業務邏輯,從而靈活自由地適應特定的業務場景的需要——也就是說要具備良好的可擴展性。比如上面提到的OMCS網絡語音視頻框架可應用于音、視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網絡監控系統等等基于網絡多媒體的應用系統;以及ESFramework通信框架能夠應用于即時通訊系統,大型多人在線游戲、在線網頁游戲、文件傳送系統、數據采集系統、分布式OA系統等任何需要分布式通信的軟件系統中——這種良好的擴展性都與“依賴注入”的使用密不可分!
·面向接口編程
談到最后,“面向接口編程”已經是呼之欲出。無論是依賴倒置、控制反轉、還是依賴注入,都已經蘊含著“面向接口編程”的思想。面向接口,就意味著面向抽象。作為哲學范疇而言,規定性少稱為抽象,規定性多稱為具體。而接口,就是程序中的一種典型的“抽象”的形式。面向抽象,就意味著面向事物的本質規定性,擺脫感性雜多的牽絆,從而把握住“必然”——而這本身就意味著自由,因為自由就是對必然的認識。
也許以上的這段論述太過“哲學”,但是“一本之理”與“萬殊之理”本身就“體用不二”——總結來看,依賴倒置、控制反轉、依賴注入都圍繞著“解耦和”的問題,而同時自始至終又都是“面向接口編程”的方法——因此,“面向接口編程”天生就是“解耦和”的好辦法。由此也印證了從“抽象”到“自由”的這一段范疇的辯證衍化。
“面向對象”與“面向接口”并非兩種不同的方法學,“面向接口”其實是“面向對象”的內在要求,是其一部分內涵的集中表述。我們對于理想軟件的期待常被概括為“高內聚,低耦合”,這也是整個現代軟件開發方法學所追求的目標。面向對象方法學作為現代軟件開發方法學的代表,本身就蘊含著“高內聚,低耦合”的思想精髓,從這個意義上來說,“面向對象”這個表述更加側重于“高內聚”,“面向接口”的表述則更加側重于“低耦合”——不過是同一事物的不同側面罷了。
除此之外,我們也能從“面向接口編程”的思想中得到“世俗”的啟迪——《論語》里面講,不患無位,患所以立;不患人之不己知,患其不能也——就是教導我們要面向“我有沒有的本事?”、“我有沒有能力?”這樣的接口,而不是面向“我有沒有搞到位子?”、“別人了不了解我?”這樣的具體。依我看,這是莫大的教誨!
2.2 六大原則詳細講解
- 單一原則
【】定義:
不要存在多于一個導致類變更的原因。通俗的說,即一個類只負責一項職責。
【】問題由來:
類T負責兩個不同的職責:職責P1,職責P2。當由于職責P1需求發生改變而需要修改類T時,有可能會導致原本運行正常的職責P2功能發生故障。 - 依賴倒置原則
就是盡量依賴接口,不要依賴具體實現,依賴一個很少變化的對象,如果你依賴的實現對象老是變化,那你豈不是也要跟著老是改動,這樣很不爽,對吧!
【】定義:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。
【】問題由來:
類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責復雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
【】解決方案:
將類A修改為依賴接口I,類B和類C各自實現接口I,類A通過接口I間接與類B或者類C發生聯系,則會大大降低修改類A的幾率。
依賴倒置原則基于這樣一個事實:相對于細節的多變性,抽象的東西要穩定的多。以抽象為基礎搭建起來的架構比以細節為基礎搭建起來的架構要穩定的多。在java中,抽象指的是接口或者抽象類,細節就是具體的實現類,使用接口或者抽象類的目的是制定好規范和契約,而不去涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。
依賴倒置原則的核心思想是面向接口編程,我們依舊用一個例子來說明面向接口編程比相對于面向實現編程好在什么地方。場景是這樣的,母親給孩子講故事,只要給她一本書,她就可以照著書給孩子講故事了 - 接口隔離原則
【】定義:
客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。
【】問題由來:
類A通過接口I依賴類B,類C通過接口I依賴類D,如果接口I對于類A和類B來說不是最小接口,則類B和類D必須去實現他們不需要的方法。
通過這兩個圖,可以很容易發現,最小接口原則,就是提取公因數到一個接口里邊,向上面的I1,方法1都提取到了這個接口中
- 里式替換原則
肯定有不少人跟我剛看到這項原則的時候一樣,對這個原則的名字充滿疑惑。其實原因就是這項原則最早是在1988年,由麻省理工學院的一位姓里的女士(Barbara Liskov)提出來的。
【】定義1:
如果對每一個類型為 T1的對象 o1,都有類型為 T2 的對象o2,使得以 T1定義的所有程序 P 在所有的對象 o1 都代換成 o2 時,程序 P 的行為沒有發生變化,那么類型 T2 是類型 T1 的子類型。
【】定義2:
所有引用基類的地方必須能透明地使用其子類的對象。
【】問題由來:
有一功能P1,由類A完成?,F需要將功能P1進行擴展,擴展后的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時,有可能會導致原有功能P1發生故障。
【】解決方案:
當使用繼承時,遵循里氏替換原則。類B繼承類A時,除添加新的方法完成新增功能P2外,盡量不要重寫父類A的方法,也盡量不要重載父類A的方法。
繼承包含這樣一層含義:
父類中凡是已經實現好的方法(相對于抽象方法而言),實際上是在設定一系列的規范和契約,雖然它不強制要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會對整個繼承體系造成破壞。而里氏替換原則就是表達了這一層含義。
繼承作為面向對象三大特性之一,在給程序設計帶來巨大便利的同時,也帶來了弊端。比如使用繼承會給程序帶來侵入性,程序的可移植性降低,增加了對象間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,并且父類修改后,所有涉及到子類的功能都有可能會產生故障。 - 迪米特原則
【】定義:
一個對象應該對其他對象保持最少的了解。
【】問題由來:
類與類之間的關系越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。
【】解決方案:
盡量降低類與類之間的耦合。
【】簡介:
迪米特法則又叫最少知道原則,最早是在1987年由美國Northeastern University的Ian Holland提出。通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對于被依賴的類來說,無論邏輯多么復雜,都盡量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外泄漏任何信息。迪米特法則還有一個更簡單的定義:只與直接的朋友通信。首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關系,只要兩個對象之間有耦合關系,我們就說這兩個對象之間是朋友關系。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們稱出現成員變量、方法參數、方法返回值中的類為直接的朋友,而出現在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作為局部變量的形式出現在類的內部。
【】看下面這個例子
舉一個例子:有一個集團公司,下屬單位有分公司和直屬部門,現在要求打印出所有下屬單位的員工ID。先來看一下違反迪米特法則的設計。
//總公司員工
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();
//為分公司人員按順序分配一個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();
//為總公司人員按順序分配一個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());
}
}
現在這個設計的主要問題出在CompanyManager中,根據迪米特法則,只與直接的朋友發生通信,而SubEmployee類并不是CompanyManager類的直接朋友(以局部變量出現的耦合不屬于直接朋友),從邏輯上講總公司只與他的分公司耦合就行了,與分公司的員工并沒有任何聯系,這樣設計顯然是增加了不必要的耦合。按照迪米特法則,應該避免類中出現這樣非直接朋友關系的耦合。修改后的代碼如下:
class SubCompanyManager{
public List<SubEmployee> getAllEmployee(){
List<SubEmployee> list = new ArrayList<SubEmployee>();
for(int i=0; i<100; i++){
SubEmployee emp = new SubEmployee();
//為分公司人員按順序分配一個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();
//為總公司人員按順序分配一個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());
}
}
}
這兩個代碼的區別,就是
避免了總公司的打印方法操作子公司的員工信息,這就是所謂的不要和非直接朋友關系的類進行通信,
- 開閉原則-沒有實際操作層面的原則,前面的原則執行好了自然就開閉了
【】定義:
一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。
【】問題由來:
在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,并且需要原有代碼經過重新測試
【】解決方案:
當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化
【】理解:
開閉原則是面向對象設計中最基礎的設計原則,它指導我們如何建立穩定靈活的系統。開閉原則可能是設計模式六項原則中定義最模糊的一個了,它只告訴我們對擴展開放,對修改關閉,可是到底如何才能做到對擴展開放,對修改關閉,并沒有明確的告訴我們。以前,如果有人告訴我“你進行設計的時候一定要遵守開閉原則”,我會覺的他什么都沒說,但貌似又什么都說了。因為開閉原則真的太虛了。
三 常見設計模式
把uml類圖和設計模式的基本概念放在前面講,是因為下面很多概念的類圖說明會用到uml類圖知識和設計模式知識,在學習六大原則和類圖的過程中,也要一并將上面的知識進行融合消化理解,學習每個設計模式,盡量都要理解在uml類圖解釋他們的說明圖和設計模式原則層面的知識!
我們很多偉大工作者為了將知識更方便的傳播,將知識進行了分類傳播,但是知識接收的過程中,如果要進行消化吸收,也必須獲取到像知識傳播者一樣足夠多的信息進行知識融合,才能夠還原作者的意圖,所以知識學習要逆傳播過程,這個很像我們的osi 7層模型一樣。
3.1 創建型模式
前面講過,社會化的分工越來越細,自然在軟件設計方面也是如此,因此對象的創建和對象的使用分開也就成為了必然趨勢。因為對象的創建會消耗掉系統的很多資源,所以單獨對對象的創建進行研究,從而能夠高效地創建對象就是創建型模式要探討的問題。這里有6個具體的創建型模式可供研究,它們分別是:
簡單工廠模式(Simple Factory)
工廠方法模式(Factory Method)
抽象工廠模式(Abstract Factory)
創建者模式(Builder)
原型模式(Prototype)
單例模式(Singleton)
說明:嚴格來說,簡單工廠模式不是GoF總結出來的23種設計模式之一
- 工廠模式
工廠方法模式 每個產品需要一個工廠子類,工廠子類太多。
抽象工廠,最大特點就是,產品有多個等級,每個等級有多個規格和參數