【硬核】23種設(shè)計模式娓娓道來,助你優(yōu)雅的編寫出漂亮代碼!
作者丨淺羽Eric
來源丨淺羽的IT小屋
我們平時使用的每一個技術(shù)棧的原理或者源碼都或多或少與設(shè)計模式的理念有關(guān)聯(lián),也可以這么說,只有更好的掌握了設(shè)計模式,我們的代碼編寫才能更規(guī)范、簡潔,效率更高。
其次,設(shè)計模式大多都是經(jīng)過我們的前輩的經(jīng)驗反復(fù)總結(jié)而成,站在巨人的肩膀上,吸收他們的經(jīng)驗教訓(xùn),我們的編碼之路才會走的更長久。
同時,在我們的面試過程中也是加分的選項,你如果將設(shè)計模式能跟面試官娓娓道來,面試官肯定會對你刮目相看的。工作中,擁有良好的設(shè)計模式思想,對于項目的開發(fā)也會有很大的幫助。
接下來,跟著小羽一起來看看我們需要了解的設(shè)計模式都有哪些呢~
前言
總體來說設(shè)計模式分為三大類:
創(chuàng)建型模式:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結(jié)構(gòu)型模式:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式:策略模式、模板方法模式、觀察者模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。
單例模式
概念
確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。
使用場景
要求生成唯一序列號的環(huán)境;
在整個項目中需要一個共享訪問點或共享數(shù)據(jù),例如一個Web頁面上的計數(shù)器,可以不用把每次刷新都記錄到數(shù)據(jù)庫中,使用單例模式保持計數(shù)器的值,并確保是線程安全的;
創(chuàng)建一個對象需要消耗的資源過多,如要訪問IO和數(shù)據(jù)庫等資源;
需要定義大量的靜態(tài)常量和靜態(tài)方法(如工具類)的環(huán)境,可以采用單例模式(當(dāng)然,也可以直接聲明為static的方式)。
代碼示例
線程安全:
public class Singleton {
private static final Singleton singleton = new Singleton();
//限制產(chǎn)生多個對象
private Singleton(){
}
//通過該方法獲得實例對象
public static Singleton getSingleton(){
return singleton;
}
//類中其他方法,盡量是 static
public static void doSomething(){
}
}
線程不安全:
public class Singleton {
private static Singleton singleton = null;
//限制產(chǎn)生多個對象
private Singleton(){
}
//通過該方法獲得實例對象
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
針對線程不安全:
在 getSingleton 方法前加 synchronized 關(guān)鍵字,也可以在 getSingleton 方法內(nèi)增加synchronized 來實現(xiàn)。
工廠模式
概念
定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
使用場景
jdbc 連接數(shù)據(jù)庫,硬件訪問,降低對象的產(chǎn)生和銷毀
結(jié)構(gòu)
簡單工廠模式:一個模塊僅需要一個工廠類,沒有必要把它產(chǎn)生出來,使用靜態(tài)的方法
多個工廠類:每個人種(具體的產(chǎn)品類)都對應(yīng)了一個創(chuàng)建者,每個創(chuàng)建者獨立負(fù)責(zé)創(chuàng)建對應(yīng)的產(chǎn)品對象,非常符合單一職責(zé)原則
代替單例模式:單例模式的核心要求就是在內(nèi)存中只有一個對象,通過工廠方法模式也可以只在內(nèi)存中生產(chǎn)一個對象
延遲初始化:ProductFactory 負(fù)責(zé)產(chǎn)品類對象的創(chuàng)建工作,并且通過 prMap 變量產(chǎn)生一個緩存,對需要再次被重用的對象保留
代碼示例
Product 為抽象產(chǎn)品類負(fù)責(zé)定義產(chǎn)品的共性,實現(xiàn)對事物最抽象的定義;
Creator 為抽象創(chuàng)建類,也就是抽象工廠,具體如何創(chuàng)建產(chǎn)品類是由具體的實現(xiàn)工廠 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;
}
}
抽象工廠模式
概念
為創(chuàng)建一組相關(guān)或相互依賴的對象提供一個接口,而且無須指定它們的具體類。
使用場景
一個對象族(或是一組沒有任何關(guān)系的對象)都有相同的約束。
涉及不同操作系統(tǒng)的時候,都可以考慮使用抽象工廠模式。
代碼示例
public abstract class AbstractCreator {
//創(chuàng)建 A 產(chǎn)品家族
public abstract AbstractProductA createProductA();
//創(chuàng)建 B 產(chǎn)品家族
public abstract AbstractProductB createProductB();
}
模板方法模式
概念
定義一個操作中的算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
使用場景
多個子類有公有的方法,并且邏輯基本相同時。
重要、復(fù)雜的算法,可以把核心算法設(shè)計為模板方法,周邊的相關(guān)細(xì)節(jié)功能則由各個子類實現(xiàn)。
重構(gòu)時,模板方法模式是一個經(jīng)常使用的模式,把相同的代碼抽取到父類中,然后通過鉤子函數(shù)(見“模板方法模式的擴展”)約束其行為。
結(jié)構(gòu)
抽象模板:AbstractClass 為抽象模板,它的方法分為兩類:
1、基本方法:也叫做基本操作,是由子類實現(xiàn)的方法,并且在模板方法被調(diào)用。
2、模板方法:可以有一個或幾個,一般是一個具體方法,也就是一個框架,實現(xiàn)對基本方法的調(diào)度,完成固定的邏輯。
注意: 為了防止惡意的操作,一般模板方法都加上 final 關(guān)鍵字,不允許被覆寫。
具體模板:實現(xiàn)父類所定義的一個或多個抽象方法,也就是父類定義的基本方法在子類中得以實現(xiàn)。
代碼示例
package templateMethod;
public class TemplateMethodPattern
{
public static void main(String[] args)
{
AbstractClass tm=new ConcreteClass();
tm.TemplateMethod();
}
}
//抽象類
abstract class AbstractClass
{
public void TemplateMethod() //模板方法
{
SpecificMethod();
abstractMethod1();
abstractMethod2();
}
public void SpecificMethod() //具體方法
{
System.out.println("抽象類中的具體方法被調(diào)用...");
}
public abstract void abstractMethod1(); //抽象方法1
public abstract void abstractMethod2(); //抽象方法2
}
//具體子類
class ConcreteClass extends AbstractClass
{
public void abstractMethod1()
{
System.out.println("抽象方法1的實現(xiàn)被調(diào)用...");
}
public void abstractMethod2()
{
System.out.println("抽象方法2的實現(xiàn)被調(diào)用...");
}
}
建造者模式
概念
將一個復(fù)雜對象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
使用場景
相同的方法,不同的執(zhí)行順序,產(chǎn)生不同的事件結(jié)果時,可以采用建造者模式。
多個部件或零件,都可以裝配到一個對象中,但是產(chǎn)生的運行結(jié)果又不相同時,則可以使用該模式。
產(chǎn)品類非常復(fù)雜,或者產(chǎn)品類中的調(diào)用順序不同產(chǎn)生了不同的效能,這個時候使用建造者模式非常合適。
結(jié)構(gòu)
Product 產(chǎn)品類:通常是實現(xiàn)了模板方法模式,也就是有模板方法和基本方法。
Builder 抽象建造者:規(guī)范產(chǎn)品的組建,一般是由子類實現(xiàn)。
ConcreteBuilder 具體建造者:實現(xiàn)抽象類定義的所有方法,并且返回一個組建好的對象。
Director 導(dǎo)演類:負(fù)責(zé)安排已有模塊的順序,然后告訴 Builder 開始建造
代碼示例
public class ConcreteProduct extends Builder {
private Product product = new Product();
//設(shè)置產(chǎn)品零件
public void setPart(){
/*
* 產(chǎn)品類內(nèi)的邏輯處理
*/
}
//組建一個產(chǎn)品
public Product buildProduct() {
return product;
}
}
代理模式
概念
為其他對象提供一種代理以控制對這個對象的訪問。
結(jié)構(gòu)
Subject 抽象主題角色:抽象主題類可以是抽象類也可以是接口,是一個最普通的業(yè)務(wù)類型定義,無特殊要求。
RealSubject 具體主題角色:也叫做被委托角色、被代理角色。它才是冤大頭,是業(yè)務(wù)邏輯的具體執(zhí)行者。
Proxy 代理主題角色:也叫做委托類、代理類。它負(fù)責(zé)對真實角色的應(yīng)用,把所有抽象主題類定義的方法、限制委托給真實主題角色實現(xiàn),并且在真實主題角色處理完畢前后做預(yù)處理和善后處理工作。
分類
普通代理:在該模式下,調(diào)用者只知代理而不用知道真實的角色是誰,屏蔽了真實角色的變更對高層模塊的影響,真實的主題角色想怎么修改就怎么修改,對高層次的模塊沒有任何的影響,只要你實現(xiàn)了接口所對應(yīng)的方法,該模式非常適合對擴展性要求較高的場合。
強制代理:強制代理的概念就是要從真實角色查找到代理角色,不允許直接訪問真實角色。高層模塊只要調(diào)用 getProxy 就可以訪問真實角色的所有方法,它根本就不需要產(chǎn)生一個代理出來,代理的管理已經(jīng)由真實角色自己完成。
區(qū)別:普通代理就是我們要知道代理的存在,然后才能訪問;強制代理則是調(diào)用者直接調(diào)用真實角色,而不用關(guān)心代理是否存在,其代理的產(chǎn)生是由真實角色決定的。
動態(tài)代理:根據(jù)被代理的接口生成所有的方法,也就是說給定一個接口,動態(tài)代理會宣稱“我已經(jīng)實現(xiàn)該接口下的所有方法了”。兩條獨立發(fā)展的線路。動態(tài)代理實現(xiàn)代理的職責(zé),業(yè)務(wù)邏輯實現(xiàn)相關(guān)的邏輯功能,兩者之間沒有必然的相互耦合的關(guān)系。通知從另一個切面切入,最終在高層模塊進(jìn)行耦合,完成邏輯的封裝任務(wù)。
意圖:橫切面編程,在不改變我們已有代碼結(jié)構(gòu)的情況下增強或控制對象的行為。
首要條件:被代理的類必須要實現(xiàn)一個接口。
代碼示例
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
原型模式
概念
用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。
使用場景
資源優(yōu)化場景:類初始化需要消化非常多的資源,這個資源包括數(shù)據(jù)、硬件資源等。
性能和安全要求的場景:通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限,則可以使用原型模式。
一個對象多個修改者的場景:一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時,可以、考慮使用原型模式拷貝多個對象供調(diào)用者使用。
優(yōu)點
原型模式實際上就是實現(xiàn) Cloneable 接口,重寫 clone()方法。
性能優(yōu)良:原型模式是在內(nèi)存二進(jìn)制流的拷貝,要比直接 new 一個對象性能好很多,特別是要在一個循環(huán)體內(nèi)產(chǎn)生大量的對象時,原型模式可以更好地體現(xiàn)其優(yōu)點。
逃避構(gòu)造函數(shù)的約束:這既是它的優(yōu)點也是缺點,直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會執(zhí)行的。
代碼示例
public class PrototypeClass implements Cloneable{
//覆寫父類 Object 方法
@Override
public PrototypeClass clone(){
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass)super.clone();
} catch (CloneNotSupportedException e) {
//異常處理
}
return prototypeClass;
}
}
中介者模式
概念
用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
使用場景
中介者模式適用于多個對象之間緊密耦合的情況,緊密耦合的標(biāo)準(zhǔn)是:在類圖中出現(xiàn)了蜘蛛網(wǎng)狀結(jié)構(gòu),即每個類都與其他的類有直接的聯(lián)系。
結(jié)構(gòu)
Mediator 抽象中介者角色:抽象中介者角色定義統(tǒng)一的接口,用于各同事角色之間的通信。
Concrete Mediator 具體中介者角色:具體中介者角色通過協(xié)調(diào)各同事角色實現(xiàn)協(xié)作行為,因此它必須依賴于各個同事角色。
Colleague 同事角色:每一個同事角色都知道中介者角色,而且與其他的同事角色通信的時候,一定要通過中介者角色協(xié)作。每個同事類的行為分為兩種:一種是同事本身的行為,比如改變對象本身的狀態(tài),處理自己的行為等,這種行為叫做自發(fā)行為(SelfMethod),與其他的同事類或中介者沒有任何的依賴;第二種是必須依賴中介者才能完成的行為,叫做依賴方法(Dep-Method)。
示例代碼
public abstract class Mediator {
//定義同事類
protected ConcreteColleague1 c1;
protected ConcreteColleague2 c2;
//通過 getter/setter 方法把同事類注入進(jìn)來
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;
}
//中介者模式的業(yè)務(wù)邏輯
public abstract void doSomething1();
public abstract void doSomething2();
}
命令模式
概念
將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數(shù)化,對請求排隊或者記錄請求日志,可以提供命令的撤銷和恢復(fù)功能。
使用場景
認(rèn)為是命令的地方就可以采用命令模式,例如,在 GUI 開發(fā)中,一個按鈕的點擊是一個命令,可以采用命令模式;模擬 DOS 命令的時候,當(dāng)然也要采用命令模式;觸發(fā)-反饋機制的處理等。
結(jié)構(gòu)
Receive 接收者角色:該角色就是干活的角色,命令傳遞到這里是應(yīng)該被執(zhí)行的,具體到我們上面的例子中就是 Group 的三個實現(xiàn)類(需求組,美工組,代碼組)。
Command 命令角色:需要執(zhí)行的所有命令都在這里聲明。
Invoker 調(diào)用者角色:接收到命令,并執(zhí)行命令。在例子中,我(項目經(jīng)理)就是這個角色。
代碼示例
public class Invoker {
private Command command;
// 設(shè)值注入
public void setCommand(Command command) {
this.command = command;
}
// 執(zhí)行命令
public void action() {
this.command.execute();
}
}
責(zé)任鏈模式
概念
使多個對象都有機會處理請求,從而避免了請求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。
職責(zé)
抽象的處理者實現(xiàn)三個職責(zé):
1、定義一個請求的處理方法 handleMessage,唯一對外開放的方法;
2、定義一個鏈的編排方法 setNext,設(shè)置下一個處理者;
3、定義了具體的請求者必須實現(xiàn)的兩個方法:定義自己能夠處理的級別getHandlerLevel 和具體的處理任務(wù) echo。
代碼示例
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{
//沒有適當(dāng)?shù)奶幚碚?,業(yè)務(wù)自行處理
} }
return response;
}
//設(shè)置下一個處理者是誰
public void setNext(Handler _handler){
this.nextHandler = _handler;
}
//每個處理者都有一個處理級別
protected abstract Level getHandlerLevel();
//每個處理者都必須實現(xiàn)處理任務(wù)
protected abstract Response echo(Request request);
}
注意事項
鏈中節(jié)點數(shù)量需要控制,避免出現(xiàn)超長鏈的情況,一般的做法是在 Handler 中設(shè)置一個最大節(jié)點數(shù)量,在 setNext 方法中判斷是否已經(jīng)是超過其閾值,超過則不允許該鏈建立,避免無意識地破壞系統(tǒng)性能。
裝飾模式
概念
動態(tài)地給一個對象添加一些額外的職責(zé)。就增加功能來說,裝飾模式相比生成子類更為靈活。
使用場景
需要擴展一個類的功能,或給一個類增加附加功能。
需要動態(tài)地給一個對象增加功能,這些功能可以再動態(tài)地撤銷。
需要為一批的兄弟類進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式。
結(jié)構(gòu)
Component 抽象構(gòu)件:Component 是一個接口或者是抽象類,就是定義我們最核心的對象,也就是最原始的對象。在裝飾模式中,必然有一個最基本、最核心、最原始的接口或抽象類充當(dāng)Component 抽象構(gòu)件。
ConcreteComponent 具體構(gòu)件:ConcreteComponent 是最核心、最原始、最基本的接口或抽象類的實現(xiàn),你要裝飾的就是它。
Decorator 裝飾角色:一般是一個抽象類,做什么用呢?實現(xiàn)接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的屬性里必然有一個 private 變量指向 Component 抽象構(gòu)件。
具體裝飾角色:兩個具體的裝飾類,你要把你最核心的、最原始的、最基本的東西裝飾成其他東西。
代碼示例
/**
* 裝飾角色
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Log
class BufferedReader implements Reader{
private Reader reader;
@Override
public void read() {
reader.read();
}
public void readLine(){
read();
log.info("并且僅僅讀取一行");
}
}
策略模式
概念
定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。
使用場景
多個類只有在算法或行為上稍有不同的場景。
算法需要自由切換的場景。
需要屏蔽算法規(guī)則的場景。
具體策略數(shù)量超過 4 個,則需要考慮使用混合模式
結(jié)構(gòu)
Context 封裝角色:它也叫做上下文角色,起承上啟下封裝作用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化。
Strategy 抽象策略角色:策略、算法家族的抽象,通常為接口,定義每個策略或算法必須具有的方法和屬性。
ConcreteStrategy 具體策略角色:實現(xiàn)抽象策略中的操作,該類含有具體的算法。
代碼示例
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;
}
//聲明一個抽象函數(shù)
public abstract int exec(int a,int b);
}
適配器模式
概念
將一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
使用場景
你有動機修改一個已經(jīng)投產(chǎn)中的接口時,適配器模式可能是最適合你的模式。比如系統(tǒng)擴展了,需要使用一個已有或新建立的類,但這個類又不符合系統(tǒng)的接口,怎么辦?詳細(xì)設(shè)計階段不要考慮使用適配器模式,使用主要場景為擴展應(yīng)用中。
類適配器
Target 目標(biāo)角色:該角色定義把其他類轉(zhuǎn)換為何種接口,也就是我們的期望接口。
Adaptee 源角色:你想把誰轉(zhuǎn)換成目標(biāo)角色,這個“誰”就是源角色,它是已經(jīng)存在的、運行良好的類或?qū)ο?,?jīng)過適配器角色的包裝,它會成為一個嶄新、靚麗的角色。
Adapter 適配器角色:適配器模式的核心角色,其他兩個角色都是已經(jīng)存在的角色,而適配器角色是需要新建立的,它的職責(zé)非常簡單:把源角色轉(zhuǎn)換為目標(biāo)角色,怎么轉(zhuǎn)換?通過繼承或是類關(guān)聯(lián)的方式。
對象適配器
不使用多繼承或繼承的方式,而是使用直接關(guān)聯(lián),或者稱為委托的方式。
對象適配器和類適配器的區(qū)別:
類適配器是類間繼承,對象適配器是對象的合成關(guān)系,也可以說是類的關(guān)聯(lián)關(guān)系,這是兩者的根本區(qū)別。實際項目中對象適配器使用到的場景相對比較多。
代碼示例
public class Adapter extends Target
{
private Adaptee adaptee;
public Adapter(Adaptee adaptee)
{
this.adaptee=adaptee;
}
public void request()
{
adaptee.specificRequest();
}
}
迭代器模式
概念
它提供一種方法訪問一個容器對象中各個元素,而又不需暴露該對象的內(nèi)部細(xì)節(jié)。
結(jié)構(gòu)
Iterator 抽象迭代器:抽象迭代器負(fù)責(zé)定義訪問和遍歷元素的接口,而且基本上是有固定的 3 個方法:first()獲得第一個元素,next()訪問下一個元素,isDone()是否已經(jīng)訪問到底部(Java 叫做 hasNext()方法)。
ConcreteIterator 具體迭代器:具體迭代器角色要實現(xiàn)迭代器接口,完成容器元素的遍歷。
Aggregate 抽象容器:容器角色負(fù)責(zé)提供創(chuàng)建具體迭代器角色的接口,必然提供一個類似createIterator()這樣的方法,在 Java 中一般是 iterator()方法。
Concrete Aggregate 具體容器:具體容器實現(xiàn)容器接口定義的方法,創(chuàng)建出容納迭代器的對象。
代碼示例
/**
* 具體迭代器
*/
public class ConcreteIterator<T> implements Iterator<T> {
private List<T> list = new ArrayList<>();
private int cursor = 0;
public boolean hasNext() {
return cursor != list.size();
}
public T next() {
T obj = null;
if (this.hasNext()) {
obj = this.list.get(cursor++);
}
return obj;
}
}
組合模式
概念
將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),使得用戶對單個對象和組合對象的使用具有一致性。
使用場景
維護(hù)和展示部分-整體關(guān)系的場景,如樹形菜單、文件和文件夾管理。
從一個整體中能夠獨立出部分模塊或功能的場景。
只要是樹形結(jié)構(gòu),就考慮使用組合模式。
結(jié)構(gòu)
Component 抽象構(gòu)件角色:定義參加組合對象的共有方法和屬性,可以定義一些默認(rèn)的行為或?qū)傩浴?/p>
Leaf 葉子構(gòu)件:葉子對象,其下再也沒有其他的分支,也就是遍歷的最小單位。
Composite 樹枝構(gòu)件:樹枝對象,它的作用是組合樹枝節(jié)點和葉子節(jié)點形成一個樹形結(jié)構(gòu)。
代碼示例
public class Composite extends Component {
//構(gòu)件容器
private ArrayList<Component> componentArrayList = new
ArrayList<Component>();
//增加一個葉子構(gòu)件或樹枝構(gòu)件
public void add(Component component){
this.componentArrayList.add(component);
}
//刪除一個葉子構(gòu)件或樹枝構(gòu)件
public void remove(Component component){
this.componentArrayList.remove(component);
}
//獲得分支下的所有葉子構(gòu)件和樹枝構(gòu)件
public ArrayList<Component> getChildren(){
return this.componentArrayList;
}
}
觀察者模式
概念
定義對象間一種一對多的依賴關(guān)系,使得每當(dāng)一個對象改變狀態(tài),則所有依賴于它的對象都會得到通知并被自動更新。
使用場景
關(guān)聯(lián)行為場景。需要注意的是,關(guān)聯(lián)行為是可拆分的,而不是“組合”關(guān)系。
事件多級觸發(fā)場景。
跨系統(tǒng)的消息交換場景,如消息隊列的處理機制。
結(jié)構(gòu)
Subject 被觀察者:定義被觀察者必須實現(xiàn)的職責(zé),它必須能夠動態(tài)地增加、取消觀察者。它一般是抽象類或者是實現(xiàn)類,僅僅完成作為被觀察者必須實現(xiàn)的職責(zé):管理觀察者并通知觀察者。
Observer 觀察者:觀察者接收到消息后,即進(jìn)行 update(更新方法)操作,對接收到的信息進(jìn)行處理。
ConcreteSubject 具體的被觀察者:定義被觀察者自己的業(yè)務(wù)邏輯,同時定義對哪些事件進(jìn)行通知。
ConcreteObserver 具體的觀察者:每個觀察在接收到消息后的處理反應(yīng)是不同,各個觀察者有自己的處理邏輯。
代碼示例
public abstract class Subject {
//定義一個觀察者數(shù)組
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();
}
}
}
門面模式
概念
要求一個子系統(tǒng)的外部與其內(nèi)部的通信必須通過一個統(tǒng)一的對象進(jìn)行。門面模式提供一個高層次的接口,使得子系統(tǒng)更易于使用。
使用場景
為一個復(fù)雜的模塊或子系統(tǒng)提供一個供外界訪問的接口
子系統(tǒng)相對獨立——外界對子系統(tǒng)的訪問只要黑箱操作即可
預(yù)防低水平人員帶來的風(fēng)險擴散
結(jié)構(gòu)
Facade 門面角色:客戶端可以調(diào)用這個角色的方法。此角色知曉子系統(tǒng)的所有功能和責(zé)任。一般情況下,本角色會將所有從客戶端發(fā)來的請求委派到相應(yīng)的子系統(tǒng)去,也就說該角色沒有實際的業(yè)務(wù)邏輯,只是一個委托類。
subsystem 子系統(tǒng)角色:可以同時有一個或者多個子系統(tǒng)。每一個子系統(tǒng)都不是一個單獨的類,而是一個類的集合。子系統(tǒng)并不知道門面的存在。對于子系統(tǒng)而言,門面僅僅是另外一個客戶端而已。
代碼模式
public class Client {
//委托的子系統(tǒng)對象
private A a= new A();
private B b= new B();
private C c= new C();
//提供外部訪問的方法
public void methodA(){
this.a.doSomething();
}
public void methodB(){
this.b.doSomething();
}
public void methodC(){
this.c.doSomething();
}
}
備忘錄模式
概念
在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。這樣以后就可將該對象恢復(fù)到原先保存的狀態(tài)。
使用場景
需要保存和恢復(fù)數(shù)據(jù)的相關(guān)狀態(tài)場景。
提供一個可回滾(rollback)的操作。
需要監(jiān)控的副本場景中。
數(shù)據(jù)庫連接的事務(wù)管理就是用的備忘錄模式。
結(jié)構(gòu)
Originator 發(fā)起人角色:記錄當(dāng)前時刻的內(nèi)部狀態(tài),負(fù)責(zé)定義哪些屬于備份范圍的狀態(tài),負(fù)責(zé)創(chuàng)建和恢復(fù)備忘錄數(shù)據(jù)。
Memento 備忘錄角色:負(fù)責(zé)存儲 Originator 發(fā)起人對象的內(nèi)部狀態(tài),在需要的時候提供發(fā)起人需要的內(nèi)部狀態(tài)。
Caretaker 備忘錄管理員角色:對備忘錄進(jìn)行管理、保存和提供備忘錄。
代碼示例
public class BeanUtils {
//把 bean 的所有屬性及數(shù)值放入到 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();
}
}
}
訪問者模式
概念
封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
使用場景
一個對象結(jié)構(gòu)包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴于其具體類的操作,也就說是用迭代器模式已經(jīng)不能勝任的情景。
需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同并且不相關(guān)的操作,而你想避免讓這些操作“污染”這些對象的類。
結(jié)構(gòu)
Visitor——抽象訪問者:抽象類或者接口,聲明訪問者可以訪問哪些元素,具體到程序中就是 visit 方法的參數(shù)定義哪些對象是可以被訪問的。
ConcreteVisitor——具體訪問者:它影響訪問者訪問到一個類后該怎么干,要做什么事情。
Element——抽象元素:接口或者抽象類,聲明接受哪一類訪問者訪問,程序上是通過 accept 方法中的參數(shù)來定義的。
ConcreteElement——具體元素:實現(xiàn) accept 方法,通常是 visitor.visit(this),基本上都形成了一種模式了。
ObjectStruture——結(jié)構(gòu)對象:元素產(chǎn)生者,一般容納在多個不同類、不同接口的容器,如 List、Set、Map 等,在項目中,一般很少抽象出這個角色。
代碼示例
public class CompensationVisitor implements Visitor {
@Override
public void Visit(Element element) {
// TODO Auto-generated method stub
Employee employee = ((Employee) element);
System.out.println(
employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));
}
}
狀態(tài)模式
概念
當(dāng)一個對象內(nèi)在狀態(tài)改變時允許其改變行為,這個對象看起來像改變了其類。
使用場景
行為隨狀態(tài)改變而改變的場景,這也是狀態(tài)模式的根本出發(fā)點,例如權(quán)限設(shè)計,人員的狀態(tài)不同即使執(zhí)行相同的行為結(jié)果也會不同,在這種情況下需要考慮使用狀態(tài)模式。
條件、分支判斷語句的替代者
結(jié)構(gòu)
State——抽象狀態(tài)角色:接口或抽象類,負(fù)責(zé)對象狀態(tài)定義,并且封裝環(huán)境角色以實現(xiàn)狀態(tài)切換。
ConcreteState——具體狀態(tài)角色:每一個具體狀態(tài)必須完成兩個職責(zé):本狀態(tài)的行為管理以及趨向狀態(tài)處理,通俗地說,就是本狀態(tài)下要做的事情,以及本狀態(tài)如何過渡到其他狀態(tài)。
Context——環(huán)境角色:定義客戶端需要的接口,并且負(fù)責(zé)具體狀態(tài)的切換。
代碼示例
//抽象狀態(tài)角色
public abstract class State {
//定義一個環(huán)境角色,提供子類訪問
protected Context context;
//設(shè)置環(huán)境角色
public void setContext(Context _context){
this.context = _context;
}
//行為1
public abstract void handle1();
//行為2
public abstract void handle2();
}
解釋器模式
概念
給定一門語言,定義它的文法的一種表示,并定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。
使用場景
重復(fù)發(fā)生的問題可以使用解釋器模式
一個簡單語法需要解釋的場景
結(jié)構(gòu)
AbstractExpression——抽象解釋器:具體的解釋任務(wù)由各個實現(xiàn)類完成,具體的解釋器分別由TerminalExpression 和 Non-terminalExpression 完成。
TerminalExpression——終結(jié)符表達(dá)式:實現(xiàn)與文法中的元素相關(guān)聯(lián)的解釋操作,通常一個解釋器模式中只有一個終結(jié)符表達(dá)式,但有多個實例,對應(yīng)不同的終結(jié)符。
NonterminalExpression——非終結(jié)符表達(dá)式:文法中的每條規(guī)則對應(yīng)于一個非終結(jié)表達(dá)式,非終結(jié)符表達(dá)式根據(jù)邏輯的復(fù)雜程度而增加,原則上每個文法規(guī)則都對應(yīng)一個非終結(jié)符表達(dá)式。
Context——環(huán)境角色:一般是用來存放文法中各個終結(jié)符所對應(yīng)的具體值,這些信息需要存放到環(huán)境角色中,很多情況下我們使用 Map 來充當(dāng)環(huán)境角色就足夠了。
代碼示例
/**
* 終結(jié)符表達(dá)式
*/
public class TerminalExpression extends AbstractExpression {
@Override
public void interpret(Context ctx) {
// 實現(xiàn)與語法規(guī)則中的終結(jié)符相關(guān)聯(lián)的解釋操作
}
}
/**
* 非終結(jié)符表達(dá)式
*/
public class NonterminalExpression extends AbstractExpression {
@Override
public void interpret(Context ctx) {
// 實現(xiàn)與語法規(guī)則中的非終結(jié)符相關(guān)聯(lián)的解釋操作
}
}
享元模式
概念
使用共享對象可有效地支持大量的細(xì)粒度的對象。
對象的信息分為兩個部分:內(nèi)部狀態(tài)(intrinsic)與外部狀態(tài)(extrinsic)。
內(nèi)部狀態(tài):內(nèi)部狀態(tài)是對象可共享出來的信息,存儲在享元對象內(nèi)部并且不會隨環(huán)境改變而改變。
外部狀態(tài):外部狀態(tài)是對象得以依賴的一個標(biāo)記,是隨環(huán)境改變而改變的、不可以共享的狀態(tài)。
使用場景
系統(tǒng)中存在大量的相似對象。
細(xì)粒度的對象都具備較接近的外部狀態(tài),而且內(nèi)部狀態(tài)與環(huán)境無關(guān),也就是說對象沒有特定身份。
需要緩沖池的場景。
結(jié)構(gòu)
Flyweight——抽象享元角色:它簡單地說就是一個產(chǎn)品的抽象類,同時定義出對象的外部狀態(tài)和內(nèi)部狀態(tài)的接口或?qū)崿F(xiàn)。
ConcreteFlyweight——具體享元角色:具體的一個產(chǎn)品類,實現(xiàn)抽象角色定義的業(yè)務(wù)。該角色中需要注意的是內(nèi)部狀態(tài)處理應(yīng)該與環(huán)境無關(guān),不應(yīng)該出現(xiàn)一個操作改變了內(nèi)部狀態(tài),同時修改了外部狀態(tài),這是絕對不允許的。
unsharedConcreteFlyweight——不可共享的享元角色:不存在外部狀態(tài)或者安全要求(如線程安全)不能夠使用共享技術(shù)的對象,該對象一般不會出現(xiàn)在享元工廠中。
FlyweightFactory——享元工廠:職責(zé)非常簡單,就是構(gòu)造一個池容器,同時提供從池中獲得對象的方法。
代碼示例
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{
//根據(jù)外部狀態(tài)創(chuàng)建享元對象
flyweight = new ConcreteFlyweight1(Extrinsic);
//放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
橋梁模式
概念
將抽象和實現(xiàn)解耦,使得兩者可以獨立地變化。
使用場景
不希望或不適用使用繼承的場景
接口或抽象類不穩(wěn)定的場景
重用性要求較高的場景
結(jié)構(gòu)
Abstraction——抽象化角色:它的主要職責(zé)是定義出該角色的行為,同時保存一個對實現(xiàn)化角色的引用,該角色一般是抽象類。
Implementor——實現(xiàn)化角色:它是接口或者抽象類,定義角色必需的行為和屬性。
RefinedAbstraction——修正抽象化角色:它引用實現(xiàn)化角色對抽象化角色進(jìn)行修正。
ConcreteImplementor——具體實現(xiàn)化角色:它實現(xiàn)接口或抽象類定義的方法和屬性。
代碼示例
public abstract class Abstraction {
//定義對實現(xiàn)化角色的引用
private Implementor imp;
//約束子類必須實現(xiàn)該構(gòu)造函數(shù)
public Abstraction(Implementor _imp){
this.imp = _imp;
}
//自身的行為和屬性
public void request(){
this.imp.doSomething();
}
//獲得實現(xiàn)化角色
public Implementor getImp(){
return imp;
}
}
總結(jié)
大家在學(xué)習(xí)設(shè)計模式的時候,不要把它看得有多難,歸根結(jié)底,都是一些概論性的總結(jié)。需要我們在平時的不斷學(xué)習(xí)和工作中,慢慢去理解它的深層原理,這樣才能靈活應(yīng)用每一種設(shè)計模式。
設(shè)計模式是在前人的總結(jié)上,對一些場景的問題的進(jìn)行解決的一種方案,設(shè)計模式不是公式,沒必要去死記硬背每一種模式,更重要的是了解它的抽象思想,以及應(yīng)用設(shè)計模式怎么更好的解決問題,可以達(dá)成什么效果。理論雖多,但是我們要把它掌握的話,對于我們的實際開發(fā)來說會解決不少的問題。
當(dāng)然后面還會繼續(xù)為大家更新關(guān)于設(shè)計原則的內(nèi)容,方便大家進(jìn)一步理解設(shè)計模式。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取