1. 程式人生 > >Java設計模式超詳細

Java設計模式超詳細

正好研究生開了這門課,叫做高階軟體設計。本人雖然瞭解c++,但是不熟,老師上課講的很深,java的設計模式比較熟,所以聽得很懂。同時呢,老師上課還講了C++的一些經典設計模式的實現(好吧,實際上是大部分),但是我這個時候基本神遊天外了。幸運的是,考試只考了java版本的,哈哈。然後考前整理了下知識,發表到部落格上,供大家參考

六大設計原則

1、開閉原則(Open Close Principle)

定義:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。

問題由來:在軟體的生命週期內,因為變化、升級和維護等原因需要對軟體原有程式碼進行修改時,可能會給舊程式碼中引入錯誤,也可能會使我們不得不對整個功能進行重構,並且需要原有程式碼經過重新測試。

解決方案:當軟體需要變化時,儘量通過擴充套件軟體實體的行為來實現變化,而不是通過修改已有的程式碼來實現變化。

2、里氏代換原則(Liskov SubstitutionPrinciple)

定義一:如果對每一個型別為 T1的物件 o1,都有型別為 T2 的物件o2,使得以 T1定義的所有程式 P 在所有的物件 o1 都代換成 o2 時,程式 P 的行為沒有發生變化,那麼型別 T2 是型別 T1 的子型別。

定義2:所有引用基類的地方必須能透明地使用其子類的物件。3、依賴倒轉原則(DependenceInversion Principle)

問題由來:有一功能P1,由類A完成。現需要將功能P1進行擴充套件,擴充套件後的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來完成,則子類B在完成新功能P2的同時,有可能會導致原有功能P1發生故障。

解決方案:當使用繼承時,遵循里氏替換原則。類B繼承類A時,除新增新的方法完成新增功能P2外,儘量不要重寫父類A的方法,也儘量不要過載父類A的方法。

繼承包含這樣一層含義:父類中凡是已經實現好的方法(相對於抽象方法而言),實際上是在設定一系列的規範和契約,雖然它不強制要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會對整個繼承體系造成破壞。而里氏替換原則就是表達了這一層含義。

缺點:比如使用繼承會給程式帶來侵入性,程式的可移植性降低,增加了物件間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,並且父類修改後,所有涉及到子類的功能都有可能會產生故障。

里氏替換原則通俗的來講就是:子類可以擴充套件父類的功能,但不能改變父類原有的功能。它包含以下4層含義

子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。

子類中可以增加自己特有的方法。

當子類的方法過載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入引數更寬鬆。

當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

3、依賴導致原則

定義:高層模組不應該依賴低層模組,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象

問題由來:類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的程式碼來達成。這種場景下,類A一般是高層模組,負責複雜的業務邏輯;類B和類C是低層模組,負責基本的原子操作;假如修改類A,會給程式帶來不必要的風險。

解決方案:將類A修改為依賴介面I,類B和類C各自實現介面I,類A通過介面I間接與類B或者類C發生聯絡,則會大大降低修改類A的機率。

傳遞依賴關係有三種方式,介面傳遞、構造方法傳遞和setter方法傳遞,相信用過Spring框架的,對依賴的傳遞方式一定不會陌生。

在實際程式設計中,我們一般需要做到如下3點:

低層模組儘量都要有抽象類或介面,或者兩者都有。

變數的宣告型別儘量是抽象類或介面。

使用繼承時遵循里氏替換原則。

依賴倒置原則的核心就是要我們面向介面程式設計,理解了面向介面程式設計,也就理解了依賴倒置。

4、介面隔離原則(Interface SegregationPrinciple)

定義:客戶端不應該依賴它不需要的介面;一個類對另一個類的依賴應該建立在最小的介面上。

問題由來:類A通過介面I依賴類B,類C通過介面I依賴類D,如果介面I對於類A和類B來說不是最小介面,則類B和類D必須去實現他們不需要的方法。

解決方案:將臃腫的介面I拆分為獨立的幾個介面,類A和類C分別與他們需要的介面建立依賴關係。也就是採用介面隔離原則。

介面隔離原則的含義是:建立單一介面,不要建立龐大臃腫的介面,儘量細化介面,介面中的方法儘量少。也就是說,我們要為各個類建立專用的介面,而不要試圖去建立一個很龐大的介面供所有依賴它的類去呼叫。在程式設計中,依賴幾個專用的介面要比依賴一個綜合的介面更靈活。介面是設計時對外部設定的“契約”,通過分散定義多個介面,可以預防外來變更的擴散,提高系統的靈活性和可維護性。

採用介面隔離原則對介面進行約束時,要注意以下幾點:

介面儘量小,但是要有限度。對介面進行細化可以提高程式設計靈活性是不掙的事實,但是如果過小,則會造成介面數量過多,使設計複雜化。所以一定要適度。

為依賴介面的類定製服務,只暴露給呼叫的類它需要的方法,它不需要的方法則隱藏起來。只有專注地為一個模組提供定製服務,才能建立最小的依賴關係。

提高內聚,減少對外互動。使介面用最少的方法去完成最多的事情。

5、迪米特法則(最少知道原則)(Demeter Principle)

定義:一個物件應該對其他物件保持最少的瞭解。

問題由來:類與類之間的關係越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。

解決方案:儘量降低類與類之間的耦合。

迪米特法則又叫最少知道原則,最早是在1987年由美國NortheasternUniversity的Ian Holland提出。通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類來說,無論邏輯多麼複雜,都儘量地的將邏輯封裝在類的內部,對外除了提供的public方法,不對外洩漏任何資訊。迪米特法則還有一個更簡單的定義:只與直接的朋友通訊。首先來解釋一下什麼是直接的朋友:每個物件都會與其他物件有耦合關係,只要兩個物件之間有耦合關係,我們就說這兩個物件之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們稱出現成員變數、方法引數、方法返回值中的類為直接的朋友,而出現在區域性變數中的類則不是直接的朋友。也就是說,陌生的類最好不要作為區域性變數的形式出現在類的內部。

6、單一職責原則

定義:不要存在多於一個導致類變更的原因。通俗的說,即一個類只負責一項職責。

問題由來:類T負責兩個不同的職責:職責P1,職責P2。當由於職責P1需求發生改變而需要修改類T時,有可能會導致原本執行正常的職責P2功能發生故障。

解決方案:遵循單一職責原則。分別建立兩個類T1、T2,使T1完成職責P1功能,T2完成職責P2功能。這樣,當修改類T1時,不會使職責P2發生故障風險;同理,當修改T2時,也不會使職責P1發生故障風險。

遵循單一職責原的優點有:可以降低類的複雜度,一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單的多;提高類的可讀性,提高系統的可維護性;變更引起的風險降低,變更是必然的,如果單一職責原則遵守的好,當修改一個功能時,可以顯著降低對其他功能的影響。

設計模式概述-24種設計模式和七大設計原則

一、建立型模式

1、抽象工廠模式(Abstract factory pattern): 提供一個介面, 用於建立相關或依賴物件的家族,而不需要指定具體類.
2、生成器模式(Builderpattern): 使用生成器模式封裝一個產品的構造過程,並允許按步驟構造. 將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示.
3、工廠模式(factorymethod pattern): 定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個. 工廠方法讓類把例項化推遲到子類.
4、原型模式(prototypepattern): 當建立給定類的例項過程很昂貴或很複雜時,就使用原形模式.
5、單例模式(Singletonpattern): 確保一個類只有一個例項, 並提供全域性訪問點.
6、多例模式(Multitionpattern): 在一個解決方案中結合兩個或多個模式,以解決一般或重複發生的問題.

二、結構型模式

1、介面卡模式(Adapter pattern): 將一個類的介面, 轉換成客戶期望的另一個介面.介面卡讓原本介面不相容的類可以合作無間. 物件介面卡使用組合, 類介面卡使用多重繼承.
2、橋接模式(Bridgepattern): 使用橋接模式通過將實現和抽象放在兩個不同的類層次中而使它們可以獨立改變.
3、組合模式(compositepattern): 允許你將物件組合成樹形結構來表現"整體/部分"層次結構. 組合能讓客戶以一致的方式處理個別對象以及物件組合.
4、裝飾者模式(decoratorpattern): 動態地將責任附加到物件上, 若要擴充套件功能, 裝飾者提供了比繼承更有彈性的替代方案.
5、外觀模式(facadepattern): 提供了一個統一的介面, 用來訪問子系統中的一群介面. 外觀定義了一個高層介面, 讓子系統更容易使用.
6、亨元模式(FlyweightPattern): 如想讓某個類的一個例項能用來提供許多"虛擬例項", 就使用蠅量模式.
7、代理模式(Proxypattern): 為另一個物件提供一個替身或佔位符以控制對這個物件的訪問.

三、行為型模式

1、責任鏈模式(Chain of responsibilitypattern): 通過責任鏈模式, 你可以為某個請求建立一個物件鏈. 每個物件依序檢查此請求並對其進行處理或者將它傳給鏈中的下一個物件.
2、命令模式(Commandpattern): 將"請求"封閉成物件, 以便使用不同的請求,佇列或者日誌來引數化其他物件. 命令模式也支援可撤銷的操作.
3、直譯器模式(Interpreterpattern): 使用直譯器模式為語言建立直譯器.
4、迭代器模式(iteratorpattern): 提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的表示.
5、中介者模式(Mediatorpattern) : 使用中介者模式來集中相關物件之間複雜的溝通和控制方式.
6、備忘錄模式(Mementopattern): 當你需要讓物件返回之前的狀態時(例如, 你的使用者請求"撤銷"), 你使用備忘錄模式.
7、觀察者模式(observerpattern): 在物件之間定義一對多的依賴,這樣一來, 當一個物件改變狀態,依賴它的物件都會收到通知, 並自動更新.
8、狀態模式(Statepattern): 允許物件在內部狀態改變時改變它的行為,物件看起來好象改了它的類.
9、策略模式(strategypattern): 定義了演算法族, 分別封閉起來, 讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶.
10、模板方法模式(Templatepattern): 在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中. 模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟.
11、訪問者模式(visitorpattern): 當你想要為一個物件的組合增加新的能力,且封裝並不重要時, 就使用訪問者模式.

--------------------------------------------------------------------------------------------------

七大設計原則:

1、單一職責原則【SINGLE RESPONSIBILITY PRINCIPLE】:一個類負責一項職責.
2、里氏替換原則【LISKOVSUBSTITUTION PRINCIPLE】:繼承與派生的規則.
3、依賴倒置原則【DEPENDENCEINVERSION PRINCIPLE】:高層模組不應該依賴低層模組,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。即針對介面程式設計,不要針對實現程式設計.
4、介面隔離原則【INTERFACESEGREGATION PRINCIPLE】:建立單一介面,不要建立龐大臃腫的介面,儘量細化介面,介面中的方法儘量少.
5、迪米特法則【LOW OFDEMETER】:低耦合,高內聚.
6、開閉原則【OPEN CLOSEPRINCIPLE】:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉.
7、組合/聚合複用原則【Composition/Aggregation ReusePrinciple(CARP) 】:儘量使用組合和聚合少使用繼承的關係來達到複用的原則.

24種設計模式詳解

建立型模式

1簡單工廠模式、工廠模式和抽象工廠模式

三種工廠模式從左到右逐步抽象,並且更具一般性。簡單工廠模式可以看做工廠模式的一種簡單化,可以歸為工廠模式的一種我們先來看看簡單工廠模式

1)     簡單工廠模式

上圖為簡單工廠模式的類圖,非常簡單。FactoryBMW類就是工廠, 它負責生產寶馬汽車。這個類生產的方式是這樣的,通過if-else語句或者switch語句分析“客戶”傳遞給它的引數,來new BMW523還是new BMW320.如下圖程式碼所示:

優點:能夠快速的生產某種製品。
缺點:當增加一種產品時,工廠只能修改自己的內部程式碼來生產這個產品,違反了開閉原則。於是,我們引出工廠方法模式。

2)     工廠方法模式
工廠方法模式通象對工廠抽象,增加了一個抽象工廠類,這個抽象工廠類能夠生產某個抽象的產品(不是具體的產品)。具體的工廠實現類通過實現這個抽象工廠的方法來生產具體的不同的產品,如下圖所示:

客戶持有的是工廠和產品的抽象。要生產某個具體的產品,就把某個抽象工廠的具體子類傳給客戶即可生產。比如我們要生產ConcreProduct1,只需要呼叫ConcreteFactory的create()方法即可。那麼這個就良好的解決了上面的問題,如果要增加產品,那麼只需要增加一個具體工廠來生產這個產品就可以了。

優點:具有良好的可拓展性,複合開閉原則
缺點:對於多產品族,比如生產一系列部件來組裝一個產品,工廠方法模式不能做到很好。於是,這又引出我們的下一個模式,抽象工廠模式。

3)     抽象工廠模式
我們先來看抽象工廠模式的類圖

然後根據類圖我們來介紹抽象工廠模式:什麼是產品族呢?像圖中的ProductA和ProductB就是兩個產品族,他們下面有一系列的功能相似或相近的產品。還是前面的寶馬車的例子。我們現在要詳細的生產寶馬了,於是我們假設我們的寶馬車由空調和外殼構成,那麼ProductA就是空調,它下面有兩個品類的空調,分別是A1,A2.ProductB就是外殼產品,下面也有兩種外殼。那麼你很自然的就想到,兩兩組合肯定會有四種BMW的車啊。但是我們的工廠只有兩個,Factory1負責組裝ProductA1和ProduchtB1,Factory2負責組裝ProductA2和ProductB2,因為其他兩個可能沒有市場,組裝起來太不好看了。這就是抽象工廠模式,每個工廠負責組裝不同的產品,而這個產品是由很多個部件構成的,每個部件又都屬於不同的產品族。
那麼問題來了,如果我們增加了新的產品族呢?啊哈,這個還真不好辦,只能修改你的工廠了。但是如果你沒有增加新的產品族,而只是在原來的產品族增加了新的產品,這個時候又增加了一個新的BMW品種了,你只需要寫一個ConcreteFactory3,來組裝必要的配件就可以了。

生成器模式(Builder pattern)

生成器模式負責將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。該模式的類圖如下:

Builder:生成器介面,面向介面程式設計原理抽象出的介面,這個介面定義生成一個產品Product的若干操作。

ConcreteBuilder:具體的生成器實現,可以有多個,用來生成不同的產品Product。

Product:具體生成器要構造的複雜物件。

Director:這個類持有一個Builder介面宣告的變數,通過初始化不同的ConcreteBuilder來建立不同的產品。

具體應用場景—匯出資料的應用框架

對於匯出資料的應用框架,通常在匯出資料上,會有一些約束的方式,比如匯出成文字格式、資料庫備份形式、Excel格式、Xml格式等。

對於匯出資料的應用框架,通常對於具體的匯出內容和格式是有要求的,加入現在有如下要求,簡單描述一下:

匯出的檔案,不管是什麼格式,都分成3個部分,分別是檔案頭、檔案體、檔案尾。

在檔案頭部分,需要描述如下資訊:分公司或者門市編號、匯出資料的日期。

在檔案體部分,需要描述如下資訊:表名稱,然後分條描述資料。

在檔案尾部分,需要描述如下資訊:輸出人。

具體的看這篇部落格吧,講的比較詳細了

生成器模式vs工廠模式:

1、生成器模式是為了構造一個複雜的產品,而且購造這個產品遵循一定的規則(相同的過程),而抽象工廠則是為了建立成族的產品(系列產品),同族產品的構造在邏輯上並不存在必然的聯絡(唯一必然的聯絡就是大家都屬於一族)。

2、生成器模式的構造方法是為了構造同一個產品,因此必須有指導者來協調進行工作,構造方法之間存在必然的業務聯絡,而抽象工廠的構造方法都是獨立去構建自己的產品物件,因此他們不存在必然的聯絡。在生成器模式中客戶端不直接呼叫構建產品部分的方法來獲取最終產品,而抽象工廠中客戶端是通過呼叫不同的工廠方法獲取不同的產品。

3.在生成器模式中,那些用來構造產品不同部分的方法一般都實現為Protected形式,以防止客戶端通過呼叫這種方法活得不可預料的結果,而抽象工廠中的這些方法必須為Public形式。否則客戶無法呼叫來獲得產品結果;

4.生成器模式的角色有生成器,產品和指導者,而抽象工廠的角色有工廠和產品。無論角色和功能怎樣變換,但所含的業務邏輯角色都應該存在,這也是兩個模式的業務本質。

原型模式(prototype pattern)

原型模式是一種建立型模式,他通過賦值一個已經存在的例項來返回新的例項,而不是新建例項。被複制的例項就是我們所稱的原型。這個原型是可定製的。

原型模式的賦值分為淺拷貝和深拷貝,順便來複習下深拷貝和淺拷貝吧。

淺拷貝: 對值型別的成員變數進行值的複製,對引用型別的成員變數只複製引用,不復制引用的物件,當修改該副本內的引用型別的變數時,會影響原來的類。

深拷貝: 對值型別的成員變數進行值的複製,對引用型別的成員變數也進行引用物件的複製,對於副本的修改,不會影響到源物件本身。

Java中的clone()方法

(1)clone()方法將物件複製了一份並返回給呼叫者。一般而言,clone()方法滿足下面規範:

①對任何的物件x,都有x.clone() != x;//克隆物件與原物件不是同一個物件

②對任何的物件x,都有x.clone().getClass()== x.getClass();//克隆物件與原物件的型別一樣

③如果物件x的equals()方法定義恰當,那麼x.clone().equals(x);應該成立。

那麼我們來看看淺拷貝的實現和深拷貝的實現。Java中Object自帶一個clone()方法,但是這個方法是淺拷貝的,為了實現深拷貝,必須讓對應的類內物件成員變數(即這個類內部的引用型別的成員變數)實現cloneable介面,重寫clone方法。

先看一下如何實現淺拷貝

//Professor沒有實現Cloneable介面,預設使用java.lang.Object類的clone()方法 

class Professor{ 

    String name; 

    int age; 

    Professor(String name,int age){ 

        this.name=name; 

        this.age=age; 

    } 

//Student實現了Cloneable介面 

class Student implements Cloneable{ 

    String name;//常量物件。 

    int age; 

    Professor p; 

    Student(String name,int age,Professor p){ 

        this.name=name; 

        this.age=age; 

        this.p=p; 

    } 

    public Object clone(){ 

        Student o=null; 

        try{ 

            o=(Student)super.clone(); 

        }catch(CloneNotSupportedException e){ 

            System.out.println(e.toString()); 

        } 

    //使用Object類的clone()方法 

        o.p=(Professor)this.p.clone(); 

        return o; 

    } 

public static void main(String[] args){ 

      Professor p=new Professor("wangwu",50); 

      Student s1=new Student("zhangsan",18,p); 

      Student s2=(Student)s1.clone(); 

      s2.p.name="lisi"; 

      s2.p.age=30; 

   //學生1的教授也變成了lisi,age為30 

   System.out.println("name="+s1.p.name+","+"age="+s1.p.age);  

我們來看個例子,我把我的程式碼複製過來,看一下如何實現深拷貝(ArrayList也可以深拷貝)。

package prototype;

public class Teacher implements Cloneable{

       String name;

       String id;

       public Teacher(String name, String id) {

              super();

              this.name = name;

              this.id = id;

       }

       @Override

       protected Object clone(){

              // TODO Auto-generated method stub

              Object o=null;

              try{

                     o=super.clone();

              }catch(Exception e){

                     e.printStackTrace();

              }

              return o;

       }

}

public class Element implements Cloneable{

       String name;

       public Element(String name) {

              super();

              this.name = name;

       }

       @Override

       protected Object clone(){

              // TODO Auto-generated method stub

              Object o = null;

              try{

                     o=super.clone();

              }catch(Exception e){

                     e.printStackTrace();

              }

              return o;

       }

       public String toString(){

              return name+ " ";

       }

}

package prototype;

import java.util.ArrayList;

public class Student implements Cloneable{

       String name;

       String id;

       Teacher tea;

       ArrayList<Element> e = new  ArrayList<>();

       public Student(String name,String id,Teacher tea){

              this.id=id;

              this.name=name;

              this.tea=tea;

       }

       @Override

       protected Object clone() {

              // TODO Auto-generated method stub

              Student s = null;

              try{

                     s=(Student)super.clone();

                     s.tea=(Teacher)this.tea.clone();

                     ArrayList<Element> newE = new ArrayList<>();

                     for(Element ele:e){

                            Element e = (Element) ele.clone();

                            newE.add(e);

                     }

                     s.e=newE;

              }catch(Exception e){

              }

              return s;

       }

       public static void main(String[] args){

              Student s1 = new Student("liming", "001", new Teacher("Li","100"));

              Element e1 = new Element("e1");

              Element e2 = new Element("e2");

              Element e3 = new Element("e3");

              s1.e.add(e1);

              s1.e.add(e2);

              s1.e.add(e3);

              Student s2 = (Student) s1.clone();

              s2.tea.name="xiaohua";

              for(Element ele:s2.e){

                     ele.name="a";

              }

              System.out.println(s1.tea.name+" "+s1.e);

              System.out.println(s2.tea.name+" "+s2.e);

       }

}

單例模式(Singleton pattern)

餓漢模式的單例是執行緒安全的:


靜態內部類也是執行緒安全的

多例模式(Multition pattern)

多例模式可以看做是單例模式的推廣,作為物件的建立模式,多例模式或多例類有如下特點:

(1)多例類可有多個例項

(2)多例類必須自己建立、管理自己的例項,並向外界提供自己的例項。

(3)根據是否有例項上限分為:有上限多例類和無上限多例類。

多例模式就是在一個類裡面一次性的建立多個例項,並將例項新增到一個list或者map中,然後獲取你想要的例項就可以了。比如語言的多例模式,如下所示:

import java.util.*;

class LingualResource

{

    private String languages = "en";

    private String region = "US";

    private String localeCode = "en_US";

    private static final String FILE_NAME = "res";

    private static Map<String,LingualResource> instances = new HashMap< String,LingualResource >();

    private Locale locale = null;

    private ResourceBundle resourceBundle = null;

    private LingualResource lingualResource;

    private LingualResource(String language,String region)

    {

        this.localeCode = language;

        this.region = region;

        localeCode = makeLocaleCode(language,region);

        locale = new Locale(language,region);

        resourceBundle = ResourceBundle.getBundle(FILE_NAME,locale);

        instances.put(localeCode,this);

    }

    private LingualResource() {}

    public synchronized static LingualResource getInstance(String language,String region)

    {

        if(instances.get(makeLocaleCode(language,region)) != null)

        {

            return instances.get(makeLocaleCode(language,region));

        } else {

            return new LingualResource(language,region);

        }

    }

    public String getLocaleString(String code)

    {

        return resourceBundle.getString(code);

    }

    private static String makeLocaleCode(String language,String region)

    {

        return language + "_" + region;

    }

}

class LingualResourceTest

{

    public static void main(String[] args)

    {

        LingualResource lr = LingualResource.getInstance("en","US");

        String usDollar = lr.getLocaleString("USD");

        System.out.println("USD=" + usDollar);

    }

}

將不同地區的語言和對應的地區進行封裝,建立多個例項,來實現多例模式

結構型模式

介面卡模式(Adapter pattern)

將一個類的介面轉換成客戶希望的另外一個介面,Adapter模式使得原本由於介面不相容而不能一起工作的類可以在一起工作。介面卡模式通過使用繼承(實現)和組合,分為兩種,分別是類介面卡模式和物件介面卡模式。

類介面卡模式:

上圖可以看出,Adaptee沒有sampleOperation2方法,而客戶端期望使用這個方法,於是我們使用Adapter繼承Adaptee,並實現Target介面。圖上沒有畫出來的是Client類,這個類持有一個Target型別的變數,只要將這個變數初始化為對應的Adapter,即可使用Adaptee的方法。

物件介面卡方法

和前面的功能是異曲同工的,只不過繼承改成了組合。Adapter持有Adaptee的物件,用這個物件來實現對應的sampleOperation1或者sampleOperation2方法。和前面相比,前面的類介面卡不需要重寫sampleOperation1方法,因為通過繼承獲得了Adaptee的對應的方法。

橋接模式(Bridge pattern)

橋接模式的目的是把變化的部分抽象出來,使得變化部分與主類分離開來,從而將多個維度的變化徹底分離。橋接模式常常應用於多個維度變化的類(即一個類變化的原因多於1個)。

橋接模式的類圖如下:

當變化的維度超過1個的時候,可以將多餘1個的維度拿出來作為抽象的類。上圖的Implementor就是一個多餘的變化維度,將這個維度的變化的可能抽象,具體的實現通過實現這個抽象介面的方式來實現(如圖中的Implementor和ConcreteImplementorA/B)。然後Abstraction也是一個變化的維度,因此它也是抽象的。這個維度持有Implementor抽象介面的一個成員變數,通過初始化不同的實際成員來實現所需的功能。Abstraction變化的維度被封裝到了外面,內部由於是抽象,抽象是穩定的。如果有更多的維度,那麼就讓Abstraction持有更多的抽象型別物件即可。

下圖介紹了一個簡單的例子。汽車有小汽車和公交車,道路有高速路和城市公路,那麼這就是兩個變化的維度,我們讓AbstractRoad作為上面類圖的Abstraction,AbstractCar作為Implementor,看一下實現的過程

abstract class AbstractRoad{ 

    AbstractCar aCar; 

    void run(){}; 

abstract class AbstractCar{ 

    void run(){}; 

class Street extends AbstractRoad{ 

    @Override 

    void run() { 

        // TODO Auto-generated method stub 

        super.run(); 

        aCar.run(); 

        System.out.println("在市區街道行駛"); 

    } 

class SpeedWay extends AbstractRoad{ 

    @Override 

    void run() { 

        // TODO Auto-generated method stub 

        super.run(); 

        aCar.run(); 

        System.out.println("在高速公路行駛"); 

    } 

class Car extends AbstractCar{ 

    @Override 

    void run() { 

        // TODO Auto-generated method stub 

        super.run(); 

        System.out.print("小汽車"); 

    } 

class Bus extends AbstractCar{ 

    @Override 

    void run() { 

        // TODO Auto-generated method stub 

        super.run(); 

        System.out.print("公交車"); 

    } 

public static void main(String[] args){ 

    AbstractRoad speedWay = new SpeedWay(); 

    speedWay.aCar = new Car(); 

    speedWay.run(); 

    AbstractRoad street = new Street(); 

    street.aCar = new Bus(); 

    street.run(); 

組合模式(composite pattern)


組合模式是一個很有意思的模式。最常見的可以適合組合模式的就是檔案目錄。比如linux的檔案系統,/根目錄下有若干的目錄,每個目錄下面又有若干的目錄,到最後目錄下面會有檔案或者目錄或者是空的資料夾。那麼根目錄/就是圖中的Component,目錄(資料夾)就是圖中的composite,檔案就是圖中的Leaf。這樣是不是就很容易理解了?這些類都實現了圖中抽象類Component的方法,只不過有的方法有具體的實現程式碼,有的僅僅是“System.out.println(“Sorry,this object can not implement thismethod”)””。下面看個例子:

package Composite;

//Component ,抽象檔案類

/**

 * Created by Jiqing on 2016/10/5.

 */

abstract class AbstractFile {

    public abstract void add(AbstractFile file);

    public abstract void remove(AbstractFile file);

    public abstract AbstractFile getChild(int i);

    public abstract void killVirus();

}

package Composite;

//圖片檔案類,一個具體的葉子Leaf

/**

 * Created by Jiqing on 2016/10/5.

 */

public class ImageFile extends AbstractFile{

    private String name;

    public ImageFile (String name) {

        this.name = name;

    }

    public void add(AbstractFile file) {

        System.out.println("對不起,不支援該方法!");

    }

    public void remove(AbstractFile file) {

        System.out.println("對不起,不支援該方法!");

    }

    public AbstractFile getChild(int i) {

        System.out.println("對不起,不支援該方法!");

        return null;

    }

    public void killVirus() {

        // 模擬防毒

        System.out.println("----對影象檔案'" + name + "'進行防毒----");

    }

}

package Composite;

//文字檔案,一個Leaf

/**

 * Created by Jiqing on 2016/10/5.

 */

public class TextFile extends AbstractFile{

    private String name;

    public TextFile (String name) {

        this.name = name;

    }

    public void add(AbstractFile file) {

        System.out.println("對不起,不支援該方法!");

    }

    public void remove(AbstractFile file) {

        System.out.println("對不起,不支援該方法!");

    }

    public AbstractFile getChild(int i) {

        System.out.println("對不起,不支援該方法!");

        return null;

    }

    public void killVirus() {

        // 模擬防毒

        System.out.println("----對文字檔案'" + name + "'進行防毒----");

    }

}

package Composite;

import java.util.ArrayList;

//資料夾類,一個composite

/**

 * Created by Jiqing on 2016/10/5.

 */

public class Folder extends AbstractFile{

    private ArrayList<AbstractFile> fileList = new ArrayList<AbstractFile>(); // 規定集合中成員型別

    private String name;

    public Folder(String name) {

        this.name = name;

    }

    public void add(AbstractFile file) {

        fileList.add(file);

    }

    public void remove(AbstractFile file) {

        fileList.remove(file);

    }

    public AbstractFile getChild(int i) {

        return (AbstractFile)fileList.get(i); // 強制轉換為型別

    }

    public void killVirus() {

        System.out.println("****對資料夾'" + name + "'進行防毒****"); // 模擬防毒

        // 遞迴呼叫成員構件的防毒方法

        for (Object obj :fileList) {

            ((AbstractFile)obj).killVirus();

        }

    }

}

package Composite;

/**

 * Created by Jiqing on 2016/10/5.

 */

public class Client {

    public static void main(String args[]) {

        AbstractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;

        folder1 = new Folder("Jim的資料");

        folder2 = new Folder("影象檔案");

        folder3 = new Folder("文字檔案");

        folder4 = new Folder("視訊檔案");

        file1 = new ImageFile("小龍女.jpg");

        file2 = new ImageFile("張無忌.gif");

        file3 = new TextFile("九陰真經.txt");

        file4 = new TextFile("葵花寶典.doc");

        file5 = new VideoFile("笑傲江湖.rmvb");

        folder2.add(file1);

        folder2.add(file2);

        folder3.add(file3);

        folder3.add(file4);

        folder4.add(file5);

        folder1.add(folder2);

        folder1.add(folder3);

        folder1.add(folder4);

        folder1.killVirus();

    }

}

裝飾者模式(decorator pattern)

裝飾者模式通過動態的給一個類新增一些額外的職責(就像給他新增一些裝飾品一樣),使用Decorator比使用子類的方式要更靈活一些。

這個類圖的含義是:Component是要被裝飾的抽象類,ConcreteComponent是一個具體的類。Decorator是一個抽象的裝飾器類,裝飾器類下面又有若干的具體的裝飾器。這個的實現也比較有意思。關鍵是Decorator持有一個Component物件,通過這個Component物件,我們可以不斷的給Component增加裝飾。比如下面的例子。

 package com.test.patten.decorator;

//Component類

 public interface Person {

   void doCoding();

}

package com.test.patten.decorator;

public class Employee implements Person {

    @Override

    public void doCoding() {

        System.out.println("程式設計師加班寫程式啊,寫程式,終於寫完了。。。");

    }

}

package com.test.patten.decorator;

//抽象裝飾器類

public abstract class Manager implements Person{

      //裝飾器增加功能

    public abstract void doCoding();

}

package com.test.patten.decorator;

//一個具體的裝飾器

public class ManagerA extends Manager {

    private Person person;//給僱員升職

    public ManagerA(Person person) {

        super();

        this.person = person;

    }

    @Override

    public void doCoding() {

        doEarlyWork();

        person.doCoding();       

    }

    /**

     * 專案經理開始前期準備工作

     */

    public void doEarlyWork() {

        System.out.println("專案經理A做需求分析");

        System.out.println("專案經理A做架構設計");

        System.out.println("專案經理A做詳細設計");

    }

}

package com.test.patten.decorator;

//另一個具體的裝飾器

public class ManagerB extends Manager {

    private Person person;//給僱員升職

    public ManagerB(Person person) {

        super();

        this.person = person;

    }

    @Override

    public void doCoding() {

        person.doCoding();

        doEndWork();

    }

    /**

     * 專案經理開始專案收尾工作

     */

    public void doEndWork() {

        System.out.println("專案經理B 在做收尾工作");

    }

}

package com.test.patten.decorator;

//main方法,多次裝飾Employee,讓Employee變得無比強大

public class Client {

    public static void main(String args[]){

        Person employee = new Employee();

        employee = new ManagerA(employee);//賦予程式猿專案經理A職責

        employee = new ManagerB(employee);//賦予程式猿專案經理B職責

        employee.doCoding();

    }

}

//輸出

專案經理A做需求分析

專案經理A做架構設計

專案經理A做詳細設計

程式設計師加班寫程式啊,寫程式,終於寫完了。。。

專案經理B 在做收尾工作

外觀模式(facade pattern)

外觀模式是一個比較簡單的模式。它的作用是將系統內部的實現隱藏,只對外提供一系列可以訪問的介面,提高安全性和可用性。

亨元模式(Flyweight Pattern)

享元模式Flyweight,在拳擊比賽指最輕量級。這樣起名字是這樣最能表達享元模式的用意。享元模式以共享的模式高效的支援大量的細粒度物件。具體來說就是:如果一個系統中存在著大量相同的物件,那麼只需要共享一份物件的拷貝,而不必為每一次使用建立新的物件。享元模式一般和單例模式一起結合使用。

Flyweight:抽象享元角色,給出一個抽象介面,以規定所有的具體享元角色需要實現的方法。

ConcreteFlyweight:具體的享元物件,必須是共享的,需要封裝Flyweight的內部狀態。

UnsharedConcreteFlyweight:有的還有一個這個具體類,繼承自Flyweight,這是非共享的享元物件。

FlyweightFactory:享元工廠,主要用來建立並管理共享的享元物件,對外提供訪問享元的介面。

下面我們來看一個具體的例子。

public interface Flyweight {

    //一個示意性方法,引數state是外蘊狀態

    public void operation(String state);

}

public class ConcreteFlyweight implements Flyweight {

    private Character intrinsicState = null;

    /**

     * 建構函式,內蘊狀態作為引數傳入

     * @param state

     */

    public ConcreteFlyweight(Character state){

        this.intrinsicState = state;

    }

    /**

     * 外蘊狀態作為引數傳入方法中,改變方法的行為,

     * 但是並不改變物件的內蘊狀態。

     */

    @Override

    public void operation(String state) {

        // TODO Auto-generated method stub

        System.out.println("Intrinsic State = " + this.intrinsicState);

        System.out.println("Extrinsic State = " + state);

    }

}

享元工廠類,由於這個類一般只有一個,因此這裡可以使用單例模式。

public class FlyweightFactory {

    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();

    public Flyweight factory(Character state){

        //先從快取中查詢物件

        Flyweight fly = files.get(state);

        if(fly == null){

            //如果物件不存在則建立一個新的Flyweight物件

            fly = new ConcreteFlyweight(state);

            //把這個新的Flyweight物件新增到快取中

            files.put(state, fly);

        }

        return fly;

    }

}

客戶端類

public class Client {

    public static void main(String[] args) {

        // TODO Auto-generated method stub

        FlyweightFactory factory = new FlyweightFactory();

        Flyweight fly = factory.factory(new Character('a'));

        fly.operation("First Call");

        fly = factory.factory(new Character('b'));

        fly.operation("Second Call");

        fly = factory.factory(new Character('a'));

        fly.operation("Third Call");

    }

}

代理模式(Proxy pattern)

代理模式一般是用來進行遠端呼叫的。比如客戶端需要遠端呼叫伺服器的功能,可以使用代理模式將所需要的功能讓代理傳遞給我們的客戶端,通過呼叫這個代理的函式,看上去就像直接呼叫這個伺服器的功能一樣。這種常見的代理模式的應用有RMI。

還有的代理呢,使用的目的是為了給被代理物件新增新的功能,增加一些新的處理。比如java 的動態代理Proxy。

動態代理Proxy

這個代理的實現由2步,第一步,讓一個類實現InvocationHandler介面並實現invoke方法。第二步,使用Proxy的靜態方法newProxyInstance方法建立一個例項,這個例項就是我們的代理。我們來看一個具體的例子;

public interface Subject  

{  

  public void doSomething();  

}  

public class RealSubject implements Subject  

{  

  public void doSomething()  

  {  

    System.out.println( "call doSomething()" );  

  }  

}  

public class ProxyHandler implements InvocationHandler  

{  

  private Object proxied;  

  public ProxyHandler( Object proxied )  

  {  

    this.proxied = proxied;  

  }  

  public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable  

  {  

    //在轉調具體目標物件之前,可以執行一些功能處理

    //轉調具體目標物件的方法

    return method.invoke( proxied, args); 

    //在轉調具體目標物件之後,可以執行一些功能處理

  }   

}

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Method;  

import java.lang.reflect.Proxy;  

import sun.misc.ProxyGenerator;  

import java.io.*;  

public class DynamicProxy  

{  

  public static void main( String args[] )  

  {  

    RealSubject real = new RealSubject();  

    Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),

     new Class[]{Subject.class},

     new ProxyHandler(real));

    proxySubject.doSomething();

    //write proxySubject class binary data to file  

    createProxyClassFile();  

  }  

  public static void createProxyClassFile()  

  {  

    String name = "ProxySubject";  

    byte[] data = ProxyGenerator.generateProxyClass( name, new Class[] { Subject.class } );  

    try 

    {  

      FileOutputStream out = new FileOutputStream( name + ".class" );  

      out.write( data );  

      out.close();  

    }  

    catch( Exception e )  

    {  

      e.printStackTrace();  

    }  

  }  

}

行為型模型

責任鏈模式(Chain of responsibility pattern)

責任鏈模式是一種物件的行為模式。在責任鏈模式裡,很多物件由每一個物件對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。

在以下條件下可考慮使用Chain of Responsibility:

1 有多個的物件可以處理一個請求,哪個物件處理該請求執行時刻自動確定。

2 你想在不明確指定接受者的情況下,想過個物件中的一個提交一個請求。

3 可處理一個請求的物件集合應該被動態指定。

這裡可以舉一個簡單的例子,比如專案經理報銷額度不能大於500,部門經理不能大於1000,超過1000需要總經理稽核。也就是說,這個可以用呼叫鏈來處理。下面來看代嗎

abstract class ConsumeHandler {

    private ConsumeHandler nextHandler;

    public ConsumeHandler getNextHandler() {

        return nextHandler;

    }

    public void setNextHandler(ConsumeHandler nextHandler) {

        this.nextHandler = nextHandler;

    }

    /** user申請人 free報銷費用 */

    public abstract void doHandler(String user, double free);

}

//專案經理

class ProjectHandler extends ConsumeHandler {

    @Override

    public void doHandler(String user, double free) {

        if (free < 500) {

            if (user.equals("lwx")) {

                System.out.println("給予報銷:" + free);

            } else {

                System.out.println("報銷不通過");

            }

        } else {

            if (getNextHandler() != null) {

                getNextHandler().doHandler(user, free);

            }

        }

    }

}

//部門經理

class DeptHandler extends ConsumeHandler {

    @Override

    public void doHandler(String user, double free) {

        if (free < 1000) {

            if (user.equals("zy")) {

                System.out.println("給予報銷:" + free);

            } else {

                System.out.println("報銷不通過");

            }

        } else {

            if (getNextHandler() != null) {

                getNextHandler().doHandler(user, free);

            }

        }

    }

}

//總經理

class GeneralHandler extends ConsumeHandler {

    @Override

    public void doHandler(String user, double free) {

        if (free >=1000) {

            if (user.equals("lwxzy")) {

                System.out.println("給予報銷:" + free);

            } else {

                System.out.println("報銷不通過");

            }

        } else {

            if (getNextHandler() != null) {

                getNextHandler().doHandler(user, free);

            }

        }

    }

}

測試下

public static void main(String[] args) {

        /*ConcreteHandler handler1 = new ConcreteHandler();

        ConcreteHandler handler2 = new ConcreteHandler();

        handler1.setNextHandler(handler2);

        handler1.doHandler();*/

        ProjectHandler projectHandler =new ProjectHandler();

        DeptHandler deptHandler =new DeptHandler();

        GeneralHandler generalHandler =new GeneralHandler();

        projectHandler.setNextHandler(deptHandler);

        deptHandler.setNextHandler(generalHandler);

        projectHandler.doHandler("lwx", 450);

        projectHandler.doHandler("lwx", 600);

        projectHandler.doHandler("zy", 600);

        projectHandler.doHandler("zy", 1500);

        projectHandler.doHandler("lwxzy", 1500);

    }

命令模式(Command pattern)

命令模式可以將一個請求封裝為一個物件(即Command)物件,從而使你可以使用不同的請求來對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。

ICommand:抽象命令,定義命令的介面

ConcreteCommand:具體命令,實現要執行的方法。他通常會持有接受者的物件,呼叫接收者的方法來完成命令操作。

Receiver:接收者,真正執行命令的物件。

Invoker:呼叫者,要求命令物件執行請求,通常會持有命令物件。可以持有很多的命令物件,是客戶端呼叫命令的入口。

/// <summary>

    /// 接收者類,知道如何實施與執行一個請求相關的操作,任何類都可能作為一個接收者。

    /// </summary>

    public class Receiver

    {

        /// <summary>

        /// 真正的命令實現

        /// </summary>

        public void Action()

        {

            Console.WriteLine("Execute request!");

        }

    }

    /// <summary>

    /// 抽象命令類,用來宣告執行操作的介面

    /// </summary>

    public interface ICommand

    {

        void Execute();

    }

    /// <summary>

    /// 具體命令類,實現具體命令。

    /// </summary>

    public class ConcereteCommand : ICommand

    {

        // 具體命令類包含有一個接收者,將這個接收者物件綁定於一個動作

        private Receiver receiver;

        public ConcereteCommand(Receiver receiver)

        {

            this.receiver = receiver;

        }

        /// <summary>

        /// 說這個實現是“虛”的,因為它是通過呼叫接收者相應的操作來實現Execute的

        /// </summary>

        public void Execute()

        {

            receiver.Action();

        }

    }

    /// <summary>

    /// 排程類,要求該命令執行這個請求

    /// </summary>

    public class Invoker

    {

        private ICommand command;

        /// <summary>

        /// 設定命令

        /// </summary>

        /// <param name="command"></param>

        public void SetCommand(ICommand command)

        {

            this.command = command;

        }

        /// <summary>

        /// 執行命令

        /// </summary>

        public void ExecuteCommand()

        {

            command.Execute();

        }

    }

客戶端程式碼

class Program

    {

        static void Main(string[] args)

        {

            Receiver receiver = new Receiver();

            ICommand command = new ConcereteCommand(receiver);

            Invoker invoker = new Invoker();

            invoker.SetCommand(command);

            invoker.ExecuteCommand();

            Console.Read();

        }

    }

下面來看看如何使用命令模式來實現redo和undo操作。可以通過增加一個命令管理類,所有命令的執行都通過命令管理類來執行。讓所有的執行過的命令都儲存在這個管理類的棧裡面。

抽象命令介面

package com.xueyoucto.xueyou; 

/**

 * Created by Administrator on 2016-07-05.

 */ 

public interface Command { 

    public void execute(); 

    public void undo(); 

具體的實現類CutCommand

package com.xueyoucto.xueyou; 

/**

 * Created by Administrator on 2016-07-05.

 */ 

public class CutCommand implements Command { 

    private String name; 

    public CutCommand(String name) { 

        this.name = name; 

    } 

    @Override 

    public void undo() { 

        System.out.println("command:" + name + " is undo"); 

    } 

    @Override 

    public void execute() { 

        System.out.println("command:" + name + " is execute"); 

    } 

具體的InsertCommand類

package com.xueyoucto.xueyou; 

/**

 * Created by Administrator on 2016-07-05.

 */ 

public class insertCommand implements Command{ 

    private String cmdName; 

    private String id; 

    private String name; 

    private int age; 

    public insertCommand(String cmdName, String id, String name, int age) { 

        this.cmdName = cmdName; 

        this.id = id; 

        this.name = name; 

        this.age = age; 

    } 

    @Override 

    public void undo() { 

        System.out.println(cmdName + " undo"); 

        System.out.println("執行delete * from table1 where id = " +id ); 

    } 

    @Override 

    public void execute() { 

        System