Java中常用的10種設計模式詳解
1. 觀察者模式
定義了物件之間的一對多的依賴,這樣一來,當一個物件改變時,它的所有的依賴者都會收到通知並自動更新。
-
對於JDK或者Andorid中都有很多地方實現了觀察者模式,比如XXXView.addXXXListenter , 當然了 XXXView.setOnXXXListener不一定是觀察者模式,因為觀察者模式是一種一對多的關係,對於setXXXListener是1對1的關係,應該叫回調。
/** * 註冊一個觀察者 */ public void registerObserver(Observer observer); /** * 移除一個觀察者 */public void removeObserver(Observer observer); /** * 通知所有觀察者 */ public void notifyObservers();
-
@Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { int index = observers.indexOf(observer); if
public ObserverUser1(Subjectsubject) { subject.registerObserver(this); } @Override public void update(String msg) { Log.e("-----ObserverUser1 ", "得到 3D 號碼:" + msg + ", 我要記下來。 "); }
-
// 建立服務號 objectFor3D = new ObjectFor3D(); // 建立兩個訂閱者 observerUser1 = new ObserverUser1(objectFor3D); observerUser2 = new ObserverUser2(objectFor3D); // 兩個觀察者,傳送兩條資訊 objectFor3D.setMsg("201610121 的3D號為:127"); objectFor3D.setMsg("20161022 的3D號為:000");
2. 工廠模式
簡單列一下這個模式的家族:
-
1、靜態工廠模式
- 這個最常見了,專案中的輔助類,TextUtil.isEmpty等,類+靜態方法。
-
2、簡單工廠模式(店裡買肉夾饃)
- 定義:通過專門定義一個類來負責建立其他類的例項,被建立的例項通常都具有共同的父類。
public RoujiaMo creatRoujiaMo(String type) { RoujiaMo roujiaMo = null; switch (type) { case "Suan": roujiaMo = new ZSuanRoujiaMo(); break; case "La": roujiaMo = new ZLaRoujiaMo(); break; case "Tian": roujiaMo = new ZTianRoujiaMo(); break; default:// 預設為酸肉夾饃 roujiaMo = new ZSuanRoujiaMo(); break; } return roujiaMo; }
-
3、工廠方法模式(開分店)
- 定義:定義一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法模式把類例項化的過程推遲到子類。
- 對比定義:
- 1、定義了建立物件的一個介面:public abstract RouJiaMo sellRoujiaMo(String type);
- 2、由子類決定例項化的類,可以看到我們的饃是子類生成的。
-
public abstract RoujiaMo sellRoujiaMo(String type);
-
4、抽象工廠模式(使用官方提供的原料)
- 定義:提供一個介面,用於建立相關的或依賴物件的家族,而不需要明確指定具體類。
- 對比定義:
- 1、提供一個介面:public interface RouJiaMoYLFactroy
- 2、用於建立相關的或依賴物件的家族 public Meat createMeat();public YuanLiao createYuanliao();我們介面用於建立一系列的原材料
/** * 準備工作 */ public void prepare(RoujiaMoYLFactory roujiaMoYLFactory) { Meet meet = roujiaMoYLFactory.creatMeet(); YuanLiao yuanLiao = roujiaMoYLFactory.creatYuanLiao(); Log.e("---RoujiaMo:", "使用官方的原料 ---" + name + ": 揉麵-剁肉-完成準備工作 yuanLiao:"+meet+"yuanLiao:"+yuanLiao); }
3. 單例設計模式
單例模式主要是為了避免因為建立了多個例項造成資源的浪費,且多個例項由於多次呼叫容易導致結果出現錯誤,而使用單例模式能夠保證整個應用中有且只有一個例項。
-
定義:只需要三步就可以保證物件的唯一性
- (1) 不允許其他程式用new物件
- (2) 在該類中建立物件
- (3) 對外提供一個可以讓其他程式獲取該物件的方法
-
對比定義:
- (1) 私有化該類的建構函式
- (2) 通過new在本類中建立一個本類物件
- (3) 定義一個公有的方法,將在該類中所建立的物件返回
private SingletonLanHan() {} private static SingletonLanHan singletonLanHanFour; public static SingletonLanHan getSingletonLanHanFour() { if (singletonLanHanFour == null) { synchronized (SingletonLanHan.class) { if (singletonLanHanFour == null) { singletonLanHanFour = new SingletonLanHan(); } } } return singletonLanHanFour; }
4. 策略模式
策略模式:定義了演算法族,分別封裝起來,讓它們之間可相互替換,此模式讓演算法的變化獨立於使用演算法的客戶。
- 以建立遊戲角色為例子:
- 最初的遊戲角色的父類
- 發現有重複程式碼後,重構後的父類
- 總結:
- 1、封裝變化(把可能變化的程式碼封裝起來)
- 2、多用組合,少用繼承(我們使用組合的方式,為客戶設定了演算法)
- 3、針對介面程式設計,不針對實現(對於Role類的設計完全的針對角色,和技能的實現沒有關係)
- 最後測試:建立角色:
RoleA roleA = new RoleA("---A"); roleA.setiDisplayBehavior(new DisplayYZ()) .setiAttackBehavior(new AttackXL()) .setiDefendBehavior(new DefendTMS()) .setiRunBehavior(new RunJCTQ()); roleA.display();// 樣子 roleA.attack();// 攻擊 roleA.run();// 逃跑 roleA.defend();// 防禦
5. 介面卡模式
定義:將一個類的介面轉換成客戶期望的另一個介面,介面卡讓原本介面不相容的類可以相互合作。這個定義還好,說介面卡的功能就是把一個介面轉成另一個介面。
-
以充電器為例項: 手機充電器一般都是5V左右吧,咱天朝的家用交流電壓220V,所以手機充電需要一個介面卡(降壓器)
-
最後測試:給手機衝個電:
Mobile mobile = new Mobile(); V5Power v5Power = new V5PowerAdapter(new V200Power()); mobile.inputPower(v5Power);
6. 命令模式
定義:將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。(簡化: 將請求封裝成物件,將動作請求者和動作執行者解耦。)
- 需求:最近智慧家電很火熱,假設現在有電視、電腦、電燈等家電,現在需要你做個遙控器控制所有家電的開關,要求做到每個按鈕對應的功能供使用者個性化,對於新買入家電要有非常強的擴充套件性。
QuickCommand quickCloseCommand = new QuickCommand(new Command[]{new LightOffCommand(light), new ComputerOffCommand(computer), new DoorCloseCommand(door)}); controlPanel.setCommands(6, quickOpenCommand); controlPanel.keyPressed(6);
controlPanel.setCommands(0, new DoorOpenCommand(door));// 開門 controlPanel.keyPressed(0);
7. 裝飾者模式
裝飾者模式:若要擴充套件功能,裝飾者提供了比整合更有彈性的替代方案,動態地將責任附加到物件上。
-
先簡單描述下裝飾者模式發揮作用的地方,當我們設計好了一個類,我們需要給這個類新增一些輔助的功能,並且不希望改變這個類的程式碼,這時候就是裝飾者模式大展雄威的時候了。這裡還體現了一個原則:類應該對擴充套件開放,對修改關閉。
-
需求:設計遊戲的裝備系統,基本要求,要可以計算出每種裝備在鑲嵌了各種寶石後的攻擊力和描述:
-
5、最後測試:計算攻擊力和檢視描述:
Log.e("---", "一個鑲嵌2顆紅寶石,1顆藍寶石的靴子: "); IEquip iEquip = new RedGemDecotator(new RedGemDecotator(new BlueGemDecotator(new ShoeEquip()))); Log.e("---", "攻擊力:" + iEquip.caculateAttack()); Log.e("---", "描述語:" + iEquip.description());
8. 外觀模式
定義:提供一個統一的介面,用來訪問子系統中的一群介面,外觀定義了一個高層的介面,讓子系統更容易使用。其實就是為了方便客戶的使用,把一群操作,封裝成一個方法。
-
需求:我比較喜歡看電影,於是買了投影儀、電腦、音響、設計了房間的燈光、買了爆米花機,然後我想看電影的時候,我需要一鍵觀影和一鍵關閉。
-
每個裝置類的開關等操作:
-
/** * 一鍵觀影 */ public void watchMovie() { computer.on(); light.down(); popcornPopper.on(); popcornPopper.makePopcorn(); projector.on(); projector.open(); player.on(); player.make3DListener(); }
-
最後測試:一鍵觀影:
new HomeTheaterFacade(computer, light, player, popcornPopper, projector).watchMovie();
9. 模板方法模式
定義:定義了一個演算法的骨架,而將一些步驟延遲到子類中,模版方法使得子類可以在不改變演算法結構的情況下,重新定義演算法的步驟。
-
需求:簡單描述一下:本公司有程式猿、測試、HR、專案經理等人,下面使用模版方法模式,記錄下所有人員的上班情況
-
模板方法模式中的三類角色
-
1、具體方法(Concrete Method)
-
2、抽象方法(Abstract Method)
-
3、鉤子方法(Hook Method)
-
// 具體方法 public final void workOneDay() { Log.e("workOneDay", "-----------------work start----------------"); enterCompany(); work(); exitCompany(); Log.e("workOneDay", "-----------------work end----------------"); } // 工作 抽象方法 public abstract void work(); // 鉤子方法 public boolean isNeedPrintDate() { return false; } private void exitCompany() { if (isNeedPrintDate()) { Log.e("exitCompany", "---" + new Date().toLocaleString() + "--->"); } Log.e("exitCompany", name + "---離開公司"); }
-
/** * 重寫父類的此方法,使可以檢視離開公司時間 */ @Override public boolean isNeedPrintDate() { return true; }
-
最後測試:
-
檢視所有人員的工作情況:
QAWorker qaWorker = new QAWorker("測試人員"); qaWorker(); HRWorker hrWorker = new HRWorker("莉莉姐"); hrWorker.workOneDay(); ...
-
檢視程式猿離開公司的時間:
ITWorker itWorker = new ITWorker("jingbin"); itWorker.workOneDay();
-
10. 狀態模式
定義:允許物件在內部狀態改變時改變它的行為,物件看起來好像修改了它的類。
-
定義又開始模糊了,理一下,當物件的內部狀態改變時,它的行為跟隨狀態的改變而改變了,看起來好像重新初始化了一個類似的。
-
需求:已自動售貨機為例(有已投幣、未投幣等狀態和投幣、投幣等方法)
-
// 放錢 public void insertMoney() { currentState.insertMoney(); } // 退錢 public void backMoney() { currentState.backMoney(); } // 轉動曲柄 public void turnCrank() { currentState.turnCrank(); if (currentState == soldState || currentState == winnerState) { currentState.dispense();//兩種情況會出貨 } } // 出商品 public void dispense() { Log.e("VendingMachineBetter", "---發出一件商品"); if (count > 0) { count--; } } // 設定對應狀態 public void setState(State state) { this.currentState = state; }
-
改進後的售貨機測試:
// 初始化售貨機,且裡面有3個商品 VendingMachineBetter machineBetter = new VendingMachineBetter(3); machineBetter.insertMoney(); machineBetter.turnCrank();