23種設計模式總結
總體來說設計模式分為三大類:
建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。
其實還有兩類:併發型模式和執行緒池模式。用一個圖片來整體描述一下:
1.單例模式(Singleton Pattern)
定義:Ensure a class has only one instance, and provide a global point of access to it.(確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。)
通用程式碼:(是執行緒安全的)
public class Singleton { private static final Singleton singleton = new Singleton(); //限制產生多個物件 private Singleton(){ } //通過該方法獲得例項物件 public static Singleton getSingleton(){ return singleton; } //類中其他方法,儘量是static public static void doSomething(){ } }
使用場景:
● 要求生成唯一序列號的環境;
● 在整個專案中需要一個共享訪問點或共享資料,例如一個Web頁面上的計數器,可以不用把每次重新整理都記錄到資料庫中,使用單例模式保持計數器的值,並確保是執行緒安全的;
● 建立一個物件需要消耗的資源過多,如要訪問IO和資料庫等資源;
● 需要定義大量的靜態常量和靜態方法(如工具類)的環境,可以採用單例模式(當然,也可以直接宣告為static的方式)。
執行緒不安全例項:
public class Singleton { private static Singleton singleton = null; //限制產生多個物件 private Singleton(){ } //通過該方法獲得例項物件 public static Singleton getSingleton(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
解決辦法:
在getSingleton方法前加synchronized關鍵字,也可以在getSingleton方法內增加synchronized來實現。最優的辦法是如通用程式碼那樣寫。
2.工廠模式
定義:Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定義一個用於建立物件的介面,讓子類決定例項化哪一個類。工廠方法使一個類的例項化延遲到其子類。)
Product為抽象產品類負責定義產品的共性,實現對事物最抽象的定義;
Creator為抽象建立類,也就是抽象工廠,具體如何建立產品類是由具體的實現工廠ConcreteCreator完成的。
具體工廠類程式碼:
public class ConcreteCreator extends Creator { public <T extends Product> T createProduct(Class<T> c){ Product product=null; try { product = (Product)Class.forName(c.getName()).newInstance(); } catch (Exception e) { //異常處理 } return (T)product; } }
簡單工廠模式:
一個模組僅需要一個工廠類,沒有必要把它產生出來,使用靜態的方法
多個工廠類:
每個人種(具體的產品類)都對應了一個建立者,每個建立者獨立負責建立對應的產品物件,非常符合單一職責原則
代替單例模式:
單例模式的核心要求就是在記憶體中只有一個物件,通過工廠方法模式也可以只在記憶體中生產一個物件
延遲初始化:
ProductFactory負責產品類物件的建立工作,並且通過prMap變數產生一個快取,對需要再次被重用的物件保留
使用場景:jdbc連線資料庫,硬體訪問,降低物件的產生和銷燬
3.抽象工廠模式(Abstract Factory Pattern)
定義:Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(為建立一組相關或相互依賴的物件提供一個介面,而且無須指定它們的具體類。)
抽象工廠模式通用類圖:
抽象工廠模式通用原始碼類圖:
抽象工廠類程式碼:
public abstract class AbstractCreator { //建立A產品家族 public abstract AbstractProductA createProductA(); //建立B產品家族 public abstract AbstractProductB createProductB(); }
使用場景:
一個物件族(或是一組沒有任何關係的物件)都有相同的約束。
涉及不同作業系統的時候,都可以考慮使用抽象工廠模式
4.模板方法模式(Template Method Pattern)
定義:Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.(定義一個操作中的演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。)
AbstractClass叫做抽象模板,它的方法分為兩類:
● 基本方法
基本方法也叫做基本操作,是由子類實現的方法,並且在模板方法被呼叫。
● 模板方法
可以有一個或幾個,一般是一個具體方法,也就是一個框架,實現對基本方法的排程,完成固定的邏輯。
注意: 為了防止惡意的操作,一般模板方法都加上final關鍵字,不允許被覆寫。
具體模板:ConcreteClass1和ConcreteClass2屬於具體模板,實現父類所定義的一個或多個抽象方法,也就是父類定義的基本方法在子類中得以實現
使用場景:
● 多個子類有公有的方法,並且邏輯基本相同時。
● 重要、複雜的演算法,可以把核心演算法設計為模板方法,周邊的相關細節功能則由各個子類實現。
● 重構時,模板方法模式是一個經常使用的模式,把相同的程式碼抽取到父類中,然後通過鉤子函式(見“模板方法模式的擴充套件”)約束其行為。
5.建造者模式(Builder Pattern)
定義:Separate the construction of a complex object from its representation so that the same construction process can create different representations.(將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。)
● Product產品類
通常是實現了模板方法模式,也就是有模板方法和基本方法,例子中的BenzModel和BMWModel就屬於產品類。
● Builder抽象建造者
規範產品的組建,一般是由子類實現。例子中的CarBuilder就屬於抽象建造者。
● ConcreteBuilder具體建造者
實現抽象類定義的所有方法,並且返回一個組建好的物件。例子中的BenzBuilder和BMWBuilder就屬於具體建造者。
● Director導演類
負責安排已有模組的順序,然後告訴Builder開始建造
使用場景:
● 相同的方法,不同的執行順序,產生不同的事件結果時,可以採用建造者模式。
● 多個部件或零件,都可以裝配到一個物件中,但是產生的執行結果又不相同時,則可以使用該模式。
● 產品類非常複雜,或者產品類中的呼叫順序不同產生了不同的效能,這個時候使用建造者模式非常合適。
建造者模式與工廠模式的不同:
建造者模式最主要的功能是基本方法的呼叫順序安排,這些基本方法已經實現了,順序不同產生的物件也不同;
工廠方法則重點是建立,建立零件是它的主要職責,組裝順序則不是它關心的。
6.代理模式(Proxy Pattern)
定義:Provide a surrogate or placeholder for another object to control access to it.(為其他物件提供一種代理以控制對這個物件的訪問。)
● Subject抽象主題角色
抽象主題類可以是抽象類也可以是介面,是一個最普通的業務型別定義,無特殊要求。
● RealSubject具體主題角色
也叫做被委託角色、被代理角色。它才是冤大頭,是業務邏輯的具體執行者。
● Proxy代理主題角色
也叫做委託類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制委託給真實主題角色實現,並且在真實主題角色處理完畢前後做預處理和善後處理工作。
普通代理和強制代理:
普通代理就是我們要知道代理的存在,也就是類似的GamePlayerProxy這個類的存在,然後才能訪問;
強制代理則是呼叫者直接呼叫真實角色,而不用關心代理是否存在,其代理的產生是由真實角色決定的。
普通代理:
在該模式下,呼叫者只知代理而不用知道真實的角色是誰,遮蔽了真實角色的變更對高層模組的影響,真實的主題角色想怎麼修改就怎麼修改,對高層次的模組沒有任何的影響,只要你實現了介面所對應的方法,該模式非常適合對擴充套件性要求較高的場合。
強制代理:
強制代理的概念就是要從真實角色查詢到代理角色,不允許直接訪問真實角色。高層模組只要呼叫getProxy就可以訪問真實角色的所有方法,它根本就不需要產生一個代理出來,代理的管理已經由真實角色自己完成。
動態代理:
根據被代理的介面生成所有的方法,也就是說給定一個介面,動態代理會宣稱“我已經實現該介面下的所有方法了”。
兩條獨立發展的線路。動態代理實現代理的職責,業務邏輯Subject實現相關的邏輯功能,兩者之間沒有必然的相互耦合的關係。通知Advice從另一個切面切入,最終在高層模組也就是Client進行耦合,完成邏輯的封裝任務。
動態代理呼叫過程示意圖:
動態代理的意圖:橫切面程式設計,在不改變我們已有程式碼結構的情況下增強或控制物件的行為。
首要條件:被代理的類必須要實現一個介面。
7.原型模式(Prototype Pattern)
定義:Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。)
原型模式通用程式碼:
public class PrototypeClass implements Cloneable{ //覆寫父類Object方法 @Override public PrototypeClass clone(){ PrototypeClass prototypeClass = null; try { prototypeClass = (PrototypeClass)super.clone(); } catch (CloneNotSupportedException e) { //異常處理 } return prototypeClass; } }
原型模式實際上就是實現Cloneable介面,重寫clone()方法。
使用原型模式的優點:
● 效能優良
原型模式是在記憶體二進位制流的拷貝,要比直接new一個物件效能好很多,特別是要在一個迴圈體內產生大量的物件時,原型模式可以更好地體現其優點。
● 逃避建構函式的約束
這既是它的優點也是缺點,直接在記憶體中拷貝,建構函式是不會執行的(參見13.4節)。
使用場景:
● 資源優化場景
類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。
● 效能和安全要求的場景
通過new產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
● 一個物件多個修改者的場景
一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。
淺拷貝和深拷貝:
淺拷貝:Object類提供的方法clone只是拷貝本物件,其物件內部的陣列、引用物件等都不拷貝,還是指向原生物件的內部元素地址,這種拷貝就叫做淺拷貝,其他的原始型別比如int、long、char、string(當做是原始型別)等都會被拷貝。
注意: 使用原型模式時,引用的成員變數必須滿足兩個條件才不會被拷貝:一是類的成員變數,而不是方法內變數;二是必須是一個可變的引用物件,而不是一個原始型別或不可變物件。
深拷貝:對私有的類變數進行獨立的拷貝
如:thing.arrayList = (ArrayList<String>)this.arrayList.clone();
8.中介者模式
定義:Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.(用一箇中介物件封裝一系列的物件互動,中介者使各物件不需要顯示地相互作用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。)
● Mediator 抽象中介者角色
抽象中介者角色定義統一的介面,用於各同事角色之間的通訊。
● Concrete Mediator 具體中介者角色
具體中介者角色通過協調各同事角色實現協作行為,因此它必須依賴於各個同事角色。
● Colleague 同事角色
每一個同事角色都知道中介者角色,而且與其他的同事角色通訊的時候,一定要通過中介者角色協作。每個同事類的行為分為兩種:一種是同事本身的行為,比如改變物件本身的狀態,處理自己的行為等,這種行為叫做自發行為(Self-Method),與其他的同事類或中介者沒有任何的依賴;第二種是必須依賴中介者才能完成的行為,叫做依賴方法(Dep-Method)。
通用抽象中介者程式碼:
public abstract class Mediator { //定義同事類 protected ConcreteColleague1 c1; protected ConcreteColleague2 c2; //通過getter/setter方法把同事類注入進來 public ConcreteColleague1 getC1() { return c1; } public void setC1(ConcreteColleague1 c1) { this.c1 = c1; } public ConcreteColleague2 getC2() { return c2; } public void setC2(ConcreteColleague2 c2) { this.c2 = c2; } //中介者模式的業務邏輯 public abstract void doSomething1(); public abstract void doSomething2(); }
ps:使用同事類注入而不使用抽象注入的原因是因為抽象類中不具有每個同事類必須要完成的方法。即每個同事類中的方法各不相同。
問:為什麼同事類要使用建構函式注入中介者,而中介者使用getter/setter方式注入同事類呢?
這是因為同事類必須有中介者,而中介者卻可以只有部分同事類。
使用場景:
中介者模式適用於多個物件之間緊密耦合的情況,緊密耦合的標準是:在類圖中出現了蜘蛛網狀結構,即每個類都與其他的類有直接的聯絡。
9.命令模式
定義:Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(將一個請求封裝成一個物件,從而讓你使用不同的請求把客戶端引數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。)
● Receive接收者角色
該角色就是幹活的角色,命令傳遞到這裡是應該被執行的,具體到我們上面的例子中就是Group的三個實現類(需求組,美工組,程式碼組)。
● Command命令角色
需要執行的所有命令都在這裡宣告。
● Invoker呼叫者角色
接收到命令,並執行命令。在例子中,我(專案經理)就是這個角色。
使用場景:
認為是命令的地方就可以採用命令模式,例如,在GUI開發中,一個按鈕的點選是一個命令,可以採用命令模式;模擬DOS命令的時候,當然也要採用命令模式;觸發-反饋機制的處理等。
10.責任鏈模式
定義:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.(使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。)
抽象處理者的程式碼:
public abstract class Handler { private Handler nextHandler; //每個處理者都必須對請求做出處理 public final Response handleMessage(Request request){ Response response = null; //判斷是否是自己的處理級別 if(this.getHandlerLevel().equals(request.getRequestLevel())){ response = this.echo(request); }else{ //不屬於自己的處理級別 //判斷是否有下一個處理者 if(this.nextHandler != null){ response = this.nextHandler.handleMessage(request); }else{ //沒有適當的處理者,業務自行處理 } } return response; } //設定下一個處理者是誰 public void setNext(Handler _handler){ this.nextHandler = _handler; } //每個處理者都有一個處理級別 protected abstract Level getHandlerLevel(); //每個處理者都必須實現處理任務 protected abstract Response echo(Request request); }
抽象的處理者實現三個職責:
一是定義一個請求的處理方法handleMessage,唯一對外開放的方法;
二是定義一個鏈的編排方法setNext,設定下一個處理者;
三是定義了具體的請求者必須實現的兩個方法:定義自己能夠處理的級別getHandlerLevel和具體的處理任務echo。
注意事項:
鏈中節點數量需要控制,避免出現超長鏈的情況,一般的做法是在Handler中設定一個最大節點數量,在setNext方法中判斷是否已經是超過其閾值,超過則不允許該鏈建立,避免無意識地破壞系統性能。
11.裝飾模式(Decorator Pattern)
定義:Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾模式相比生成子類更為靈活。)
● Component抽象構件
Component是一個介面或者是抽象類,就是定義我們最核心的物件,也就是最原始的物件,如上面的成績單。
注意:在裝飾模式中,必然有一個最基本、最核心、最原始的介面或抽象類充當Component抽象構件。
● ConcreteComponent 具體構件
ConcreteComponent是最核心、最原始、最基本的介面或抽象類的實現,你要裝飾的就是它。
● Decorator裝飾角色
一般是一個抽象類,做什麼用呢?實現介面或者抽象方法,它裡面可不一定有抽象的方法呀,在它的屬性裡必然有一個private變數指向Component抽象構件。
● 具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾類,你要把你最核心的、最原始的、最基本的東西裝飾成其他東西,上面的例子就是把一個比較平庸的成績單裝飾成家長認可的成績單。
使用場景:
● 需要擴充套件一個類的功能,或給一個類增加附加功能。
● 需要動態地給一個物件增加功能,這些功能可以再動態地撤銷。
● 需要為一批的兄弟類進行改裝或加裝功能,當然是首選裝飾模式。
12.策略模式(Strategy Pattern)
定義:Define a family of algorithms,encapsulate each one,and make them interchangeable.(定義一組演算法,將每個演算法都封裝起來,並且使它們之間可以互換。)
● Context封裝角色
它也叫做上下文角色,起承上啟下封裝作用,遮蔽高層模組對策略、演算法的直接訪問,封裝可能存在的變化。
● Strategy抽象策略角色
策略、演算法家族的抽象,通常為介面,定義每個策略或演算法必須具有的方法和屬性。各位看官可能要問了,類圖中的AlgorithmInterface是什麼意思,嘿嘿,algorithm是“運演算法則”的意思,結合起來意思就明白了吧。
● ConcreteStrategy具體策略角色(多個)
實現抽象策略中的操作,該類含有具體的演算法。
使用場景:
● 多個類只有在演算法或行為上稍有不同的場景。
● 演算法需要自由切換的場景。
● 需要遮蔽演算法規則的場景。
注意事項:具體策略數量超過4個,則需要考慮使用混合模式
策略模式擴充套件:策略列舉
public enum Calculator { //加法運算 ADD("+"){ public int exec(int a,int b){ return a+b; } }, //減法運算 SUB("-"){ public int exec(int a,int b){ return a - b; } }; String value = ""; //定義成員值型別 private Calculator(String _value){ this.value = _value; } //獲得列舉成員的值 public String getValue(){ return this.value; } //宣告一個抽象函式 public abstract int exec(int a,int b); }
定義:
● 它是一個列舉。
● 它是一個濃縮了的策略模式的列舉。
注意:
受列舉型別的限制,每個列舉項都是public、final、static的,擴充套件性受到了一定的約束,因此在系統開發中,策略列舉一般擔當不經常發生變化的角色。
致命缺陷:
所有的策略都需要暴露出去,由客戶端決定使用哪一個策略。
13.介面卡模式(Adapter Pattern)
定義:Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.(將一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。)
類介面卡:
● Target目標角色
該角色定義把其他類轉換為何種介面,也就是我們的期望介面,例子中的IUserInfo介面就是目標角色。
● Adaptee源角色
你想把誰轉換成目標角色,這個“誰”就是源角色,它是已經存在的、執行良好的類或物件,經過介面卡角色的包裝,它會成為一個嶄新、靚麗的角色。
● Adapter介面卡角色
介面卡模式的核心角色,其他兩個角色都是已經存在的角色,而介面卡角色是需要新建立的,它的職責非常簡單:把源角色轉換為目標角色,怎麼轉換?通過繼承或是類關聯的方式。
使用場景:
你有動機修改一個已經投產中的介面時,介面卡模式可能是最適合你的模式。比如系統擴充套件了,需要使用一個已有或新建立的類,但這個類又不符合系統的介面,怎麼辦?使用介面卡模式,這也是我們例子中提到的。
注意事項:
詳細設計階段不要考慮使用介面卡模式,使用主要場景為擴充套件應用中。
物件介面卡:
物件介面卡和類介面卡的區別:
類介面卡是類間繼承,物件介面卡是物件的合成關係,也可以說是類的關聯關係,這是兩者的根本區別。(實際專案中物件介面卡使用到的場景相對比較多)。
14.迭代器模式(Iterator Pattern)
定義:Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一種方法訪問一個容器物件中各個元素,而又不需暴露該物件的內部細節。)
● Iterator抽象迭代器
抽象迭代器負責定義訪問和遍歷元素的介面,而且基本上是有固定的3個方法:first()獲得第一個元素,next()訪問下一個元素,isDone()是否已經訪問到底部(Java叫做hasNext()方法)。
● ConcreteIterator具體迭代器
具體迭代器角色要實現迭代器介面,完成容器元素的遍歷。
● Aggregate抽象容器
容器角色負責提供建立具體迭代器角色的介面,必然提供一個類似createIterator()這樣的方法,在Java中一般是iterator()方法。
● Concrete Aggregate具體容器
具體容器實現容器介面定義的方法,創建出容納迭代器的物件。
ps:迭代器模式已經被淘汰,java中已經把迭代器運用到各個聚集類(collection)中了,使用java自帶的迭代器就已經滿足我們的需求了。
15.組合模式((Composite Pattern))
定義:Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.(將物件組合成樹形結構以表示“部分-整體”的層次結構,使得使用者對單個物件和組合物件的使用具有一致性。)
● Component抽象構件角色
定義參加組合物件的共有方法和屬性,可以定義一些預設的行為或屬性,比如我們例子中的getInfo就封裝到了抽象類中。
● Leaf葉子構件
葉子物件,其下再也沒有其他的分支,也就是遍歷的最小單位。
● Composite樹枝構件
樹枝物件,它的作用是組合樹枝節點和葉子節點形成一個樹形結構。
樹枝構件的通用程式碼:
public class Composite extends Component { //構件容器 private ArrayList<Component> componentArrayList = new ArrayList<Component>(); //增加一個葉子構件或樹枝構件 public void add(Component component){ this.componentArrayList.add(component); } //刪除一個葉子構件或樹枝構件 public void remove(Component component){ this.componentArrayList.remove(component); } //獲得分支下的所有葉子構件和樹枝構件 public ArrayList<Component> getChildren(){ return this.componentArrayList; } }
使用場景:
● 維護和展示部分-整體關係的場景,如樹形選單、檔案和資料夾管理。
● 從一個整體中能夠獨立出部分模組或功能的場景。
注意:
只要是樹形結構,就考慮使用組合模式。
16.觀察者模式(Observer Pattern)
定義:Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。)
● Subject被觀察者
定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類或者是實現類,僅僅完成作為被觀察者必須實現的職責:管理觀察者並通知觀察者。
● Observer觀察者
觀察者接收到訊息後,即進行update(更新方法)操作,對接收到的資訊進行處理。
● ConcreteSubject具體的被觀察者
定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。
● ConcreteObserver具體的觀察者
每個觀察在接收到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。
被觀察者通用程式碼:
public abstract class Subject { //定義一個觀察者陣列 private Vector<Observer> obsVector = new Vector<Observer>(); //增加一個觀察者 public void addObserver(Observer o){ this.obsVector.add(o); } //刪除一個觀察者 public void delObserver(Observer o){ this.obsVector.remove(o); } //通知所有觀察者 public void notifyObservers(){ for(Observer o:this.obsVector){ o.update(); } } }
使用場景:
● 關聯行為場景。需要注意的是,關聯行為是可拆分的,而不是“組合”關係。
● 事件多級觸發場景。
● 跨系統的訊息交換場景,如訊息佇列的處理機制。
注意:
● 廣播鏈的問題
在一個觀察者模式中最多出現一個物件既是觀察者也是被觀察者,也就是說訊息最多轉發一次(傳遞兩次)。
● 非同步處理問題
觀察者比較多,而且處理時間比較長,採用非同步處理來考慮執行緒安全和佇列的問題。
17.門面模式(Facade Pattern)
定義:Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.(要求一個子系統的外部與其內部的通訊必須通過一個統一的物件進行。門面模式提供一個高層次的介面,使得子系統更易於使用。)
● Facade門面角色 客戶端可以呼叫這個角色的方法。此角色知曉子系統的所有功能和責任。一般情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去,也就說該角色沒有實際的業務邏輯,只是一個委託類。 ● subsystem子系統角色 可以同時有一個或者多個子系統。每一個子系統都不是一個單獨的類,而是一個類的集合。子系統並不知道門面的存在。對於子系統而言,門面僅僅是另外一個客戶端而已。
使用場景:
● 為一個複雜的模組或子系統提供一個供外界訪問的介面
● 子系統相對獨立——外界對子系統的訪問只要黑箱操作即可
● 預防低水平人員帶來的風險擴散
注意:
●一個子系統可以有多個門面
●門面不參與子系統內的業務邏輯
18.備忘錄模式(Memento Pattern)
定義:Without violating encapsulation,capture and externalize an object's internal state so that the object can be restored to this state later.(在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可將該物件恢復到原先儲存的狀態。)
● Originator發起人角色
記錄當前時刻的內部狀態,負責定義哪些屬於備份範圍的狀態,負責建立和恢復備忘錄資料。
● Memento備忘錄角色(簡單的javabean)
負責儲存Originator發起人物件的內部狀態,在需要的時候提供發起人需要的內部狀態。
● Caretaker備忘錄管理員角色(簡單的javabean)
對備忘錄進行管理、儲存和提供備忘錄。
使用場景:
● 需要儲存和恢復資料的相關狀態場景。
● 提供一個可回滾(rollback)的操作。
● 需要監控的副本場景中。
● 資料庫連線的事務管理就是用的備忘錄模式。
注意:
●備忘錄的生命期
●備忘錄的效能
不要在頻繁建立備份的場景中使用備忘錄模式(比如一個for迴圈中)。
clone方式備忘錄:
● 發起人角色融合了發起人角色和備忘錄角色,具有雙重功效
多狀態的備忘錄模式
● 增加了一個BeanUtils類,其中backupProp是把發起人的所有屬性值轉換到HashMap中,方便備忘錄角色儲存。restoreProp方法則是把HashMap中的值返回到發起人角色中。
BeanUtil工具類程式碼:
public class BeanUtils { //把bean的所有屬性及數值放入到Hashmap中 public static HashMap<String,Object> backupProp(Object bean){ HashMap<String,Object> result = new HashMap<String,Object>(); try { //獲得Bean描述 BeanInfo beanInfo=Introspector.getBeanInfo(bean.getClass()); //獲得屬性描述 PropertyDescriptor[] descriptors=beanInfo.getPropertyDescriptors(); //遍歷所有屬性 for(PropertyDescriptor des:descriptors){ //屬性名稱 String fieldName = des.getName(); //讀取屬性的方法 Method getter = des.getReadMethod(); //讀取屬性值 Object fieldValue=getter.invoke(bean,new Object[]{}); if(!fieldName.equalsIgnoreCase("class")){ result.put(fieldName, fieldValue); } } } catch (Exception e) { //異常處理 } return result; } //把HashMap的值返回到bean中 public static void restoreProp(Object bean,HashMap<String,Object> propMap){ try { //獲得Bean描述 BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); //獲得屬性描述 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); //遍歷所有屬性 for(PropertyDescriptor des:descriptors){ //屬性名稱 String fieldName = des.getName(); //如果有這個屬性 if(propMap.containsKey(fieldName)){ //寫屬性的方法 Method setter = des.getWriteMethod(); setter.invoke(bean, new Object[]{propMap.get(fieldName)}); } } } catch (Exception e) { //異常處理 System.out.println("shit"); e.printStackTrace(); } } }
多備份的備忘錄:略
封裝得更好一點:保證只能對發起人可讀
●建立一個空介面IMemento——什麼方法屬性都沒有的介面,然後在發起人Originator類中建立一個內建類(也叫做類中類)Memento實現IMemento介面,同時也實現自己的業務邏輯。
19.訪問者模式(Visitor Pattern)
定義:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封裝一些作用於某種資料結構中的各元素的操作,它可以在不改變資料結構的前提下定義作用於這些元素的新的操作。)
● Visitor——抽象訪問者
抽象類或者介面,宣告訪問者可以訪問哪些元素,具體到程式中就是visit方法的引數定義哪些物件是可以被訪問的。
● ConcreteVisitor——具體訪問者
它影響訪問者訪問到一個類後該怎麼幹,要做什麼事情。
● Element——抽象元素
介面或者抽象類,宣告接受哪一類訪問者訪問,程式上是通過accept方法中的引數來定義的。
● ConcreteElement——具體元素
實現accept方法,通常是visitor.visit(this),基本上都形成了一種模式了。
● ObjectStruture——結構物件
元素產生者,一般容納在多個不同類、不同介面的容器,如List、Set、Map等,在專案中,一般很少抽象出這個角色。
使用場景:
● 一個物件結構包含很多類物件,它們有不同的介面,而你想對這些物件實施一些依賴於其具體類的操作,也就說是用迭代器模式已經不能勝任的情景。
● 需要對一個物件結構中的物件進行很多不同並且不相關的操作,而你想避免讓這些操作“汙染”這些物件的類。
20.狀態模式(複雜)
定義:Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。)
● State——抽象狀態角色
介面或抽象類,負責物件狀態定義,並且封裝環境角色以實現狀態切換。
● ConcreteState——具體狀態角色
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,通俗地說,就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態。
● Context——環境角色
定義客戶端需要的介面,並且負責具體狀態的切換。
使用場景:
● 行為隨狀態改變而改變的場景
這也是狀態模式的根本出發點,例如許可權設計,人員的狀態不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。
● 條件、分支判斷語句的替代者
注意:
狀態模式適用於當某個物件在它的狀態發生改變時,它的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時物件的狀態最好不要超過5個。
21.直譯器模式(Interpreter Pattern)(少用)
定義:Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(給定一門語言,定義它的文法的一種表示,並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。)
● AbstractExpression——抽象直譯器
具體的解釋任務由各個實現類完成,具體的直譯器分別由TerminalExpression和Non-terminalExpression完成。
● TerminalExpression——終結符表示式
實現與文法中的元素相關聯的解釋操作,通常一個直譯器模式中只有一個終結符表示式,但有多個例項,對應不同的終結符。具體到我們例子就是VarExpression類,表示式中的每個終結符都在棧中產生了一個VarExpression物件。
● NonterminalExpression——非終結符表示式
文法中的每條規則對應於一個非終結表示式,具體到我們的例子就是加減法規則分別對應到AddExpression和SubExpression兩個類。非終結符表示式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結符表示式。
● Context——環境角色
具體到我們的例子中是採用HashMap代替。
使用場景:
● 重複發生的問題可以使用直譯器模式
● 一個簡單語法需要解釋的場景
注意:
儘量不要在重要的模組中使用直譯器模式,否則維護會是一個很大的問題。在專案中可以使用shell、JRuby、Groovy等指令碼語言來代替直譯器模式,彌補Java編譯型語言的不足。
22.享元模式(Flyweight Pattern)
定義:Use sharing to support large numbers of fine-grained objects efficiently.(使用共享物件可有效地支援大量的細粒度的物件。)
物件的資訊分為兩個部分:內部狀態(intrinsic)與外部狀態(extrinsic)。
● 內部狀態
內部狀態是物件可共享出來的資訊,儲存在享元物件內部並且不會隨環境改變而改變。
● 外部狀態
外部狀態是物件得以依賴的一個標記,是隨環境改變而改變的、不可以共享的狀態。
● Flyweight——抽象享元角色
它簡單地說就是一個產品的抽象類,同時定義出物件的外部狀態和內部狀態的介面或實現。
● ConcreteFlyweight——具體享元角色
具體的一個產品類,實現抽象角色定義的業務。該角色中需要注意的是內部狀態處理應該與環境無關,不應該出現一個操作改變了內部狀態,同時修改了外部狀態,這是絕對不允許的。
● unsharedConcreteFlyweight——不可共享的享元角色
不存在外部狀態或者安全要求(如執行緒安全)不能夠使用共享技術的物件,該物件一般不會出現在享元工廠中。
● FlyweightFactory——享元工廠
職責非常簡單,就是構造一個池容器,同時提供從池中獲得物件的方法。
享元工廠的程式碼:
public class FlyweightFactory { //定義一個池容器 private static HashMap<String,Flyweight> pool= new HashMap<String,Flyweight>(); //享元工廠 public static Flyweight getFlyweight(String Extrinsic){ //需要返回的物件 Flyweight flyweight = null; //在池中沒有該物件 if(pool.containsKey(Extrinsic)){ flyweight = pool.get(Extrinsic); }else{ //根據外部狀態建立享元物件 flyweight = new ConcreteFlyweight1(Extrinsic); //放置到池中 pool.put(Extrinsic, flyweight); } return flyweight; } }
使用場景:
● 系統中存在大量的相似物件。
● 細粒度的物件都具備較接近的外部狀態,而且內部狀態與環境無關,也就是說物件沒有特定身份。
● 需要緩衝池的場景。
注意:
● 享元模式是執行緒不安全的,只有依靠經驗,在需要的地方考慮一下執行緒安全,在大部分場景下不用考慮。物件池中的享元物件儘量多,多到足夠滿足為止。
● 效能安全:外部狀態最好以java的基本型別作為標誌,如String,int,可以提高效率。
23.橋樑模式(Bridge Pattern)
定義:Decouple an abstraction from its implementation so that the two can vary independently.(將抽象和實現解耦,使得兩者可以獨立地變化。)
● Abstraction——抽象化角色
它的主要職責是定義出該角色的行為,同時儲存一個對實現化角色的引用,該角色一般是抽象類。
● Implementor——實現化角色
它是介面或者抽象類,定義角色必需的行為和屬性。
● RefinedAbstraction——修正抽象化角色
它引用實現化角色對抽象化角色進行修正。
● ConcreteImplementor——具體實現化角色
它實現介面或抽象類定義的方法和屬性。
使用場景:
● 不希望或不適用使用繼承的場景
● 介面或抽象類不穩定的場景
● 重用性要求較高的場景
注意:
發現類的繼承有N層時,可以考慮使用橋樑模式。橋樑模式主要考慮如何拆分抽象和實現。
設計原則:
●Single Responsibility Principle:單一職責原則
單一職責原則有什麼好處:
● 類的複雜性降低,實現什麼職責都有清晰明確的定義;
● 可讀性提高,複雜性降低,那當然可讀性提高了;
● 可維護性提高,可讀性提高,那當然更容易維護了;
●變更引起的風險降低,變更是必不可少的,如果介面的單一職責做得好,一個介面修改只對相應的實現類有影響,對其他的介面無影響,這對系統的擴充套件性、維護性都有非常大的幫助。
ps:介面一定要做到單一職責,類的設計儘量做到只有一個原因引起變化。
單一職責原則提出了一個編寫程式的標準,用“職責”或“變化原因”來衡量介面或類設計得是否優良,但是“職責”和“變化原因”都是不可度量的,因專案而異,因環境而異。
● Liskov Substitution Principle:里氏替換原則
定義:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
(所有引用基類的地方必須能透明地使用其子類的物件。)
通俗點講,只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能適應。
定義中包含的四層含義:
1.子類必須完全實現父類的方法
2.子類可以有自己的個性
3.覆蓋或實現父類的方法時輸入引數可以被放大
如果父類的輸入引數型別大於子類的輸入引數型別,會出現父類存在的地方,子類未必會存在,因為一旦把子類作為引數傳入,呼叫者很可能進入子類的方法範疇。
4. 覆寫或實現父類的方法時輸出結果可以被縮小
父類的一個方法的返回值是一個型別T,子類的相同方法(過載或覆寫)的返回值為S,那麼里氏替換原則就要求S必須小於等於T,也就是說,要麼S和T是同一個型別,要麼S是T的子類。
● Interface Segregation Principle:介面隔離原則
介面分為兩種:
例項介面(Object Interface):Java中的類也是一種介面
類介面(Class Interface): Java中經常使用Interface關鍵字定義的介面
隔離:建立單一介面,不要建立臃腫龐大的介面;即介面要儘量細化,同時介面中的方法要儘量少。
介面隔離原則與單一職責原則的不同:介面隔離原則與單一職責的審視角度是不相同的,單一職責要求的是類和介面職責單一,注重的是職責,這是業務邏輯上的劃分,而介面隔離原則要求介面的方法儘量少。
● Dependence Inversion Principle:依賴倒置原則
原始定義:
①高層模組不應該依賴低層模組,兩者都應該依賴其抽象;
②抽象不應該依賴細節(實現類);
③細節應該依賴抽象。
依賴倒置原則在java語言中的體現:
①模組間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是通過介面或抽象類產生的;
②介面或抽象類不依賴於實現類;
③實現類依賴介面或抽象類。
依賴的三種寫法:
①建構函式傳遞依賴物件(建構函式注入)
②Setter方法傳遞依賴物件(setter依賴注入)
③介面宣告依賴物件(介面注入)
使用原則:
依賴倒置原則的本質就是通過抽象(介面或抽象類)使各個類或模組的實現彼此獨立,不互相影響,實現模組間的鬆耦合,我們怎麼在專案中使用這個規則呢?只要遵循以下的幾個規則就可以:
①每個類儘量都有介面或抽象類,或者抽象類和介面兩者都具備
②變數的表面型別儘量是介面或者是抽象類
③任何類都不應該從具體類派生(只要不超過兩層的繼承是可以忍受的)
④儘量不要複寫基類的方法
⑤結合里氏替換原則使用
●Open Closed Principle:開閉原則
定義:軟體實體應該對擴充套件開放,對修改關閉。
其含義是說一個軟體實體應該通過擴充套件來實現變化,而不是通過修改已有的程式碼來實現變化。
軟體實體:專案或軟體產品中按照一定的邏輯規則劃分的模組、抽象和類、方法。
變化的三種類型:
①邏輯變化
只變化一個邏輯,而不涉及其他模組,比如原有的一個演算法是a*b+c,現在需要修改為a*b*c,可以通過修改原有類中的方法的方式來完成,前提條件是所有依賴或關聯類都按照相同的邏輯處理。
②子模組變化
一個模組變化,會對其他的模組產生影響,特別是一個低層次的模組變化必然引起高層模組的變化,因此在通過擴充套件完成變化時,高層次的模組修改是必然的。
③可見檢視變化
可見檢視是提供給客戶使用的介面,如JSP程式、Swing介面等,該部分的變化一般會引起連鎖反應(特別是在國內做專案,做歐美的外包專案一般不會影響太大)。可以通過擴充套件來完成變化,這要看我們原有的設計是否靈活。