設計模式乾貨

在這裏插入圖片描述
前言:
設計模式描述的是類與類之間的關係.常常可以和真實世界模型對比
需要code基礎,最終都需要code實現。23中設計模式,核心就是看23個小程序.
需要的uml:類圖:屬性(變量)+操作; 類圖關係:繼承,聚合(組合),依賴(使用);
notes:聚合與組合區別:舉例:樹和樹葉是組合關係,一旦樹枯死了,樹葉也就不能存活了;車和車輪是聚合關係,車輪就算沒有安裝在汽車上一樣可以存在.
0 創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、 命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
創建模式解決的是對象創建以及存在方式相關設計問題的,例如單例,工廠等,而結構模式主要解決的是類之間結構化關係的,
行爲模式不僅描述對象或類的模式,還描述它們之間的通信模式。行爲對象模式使用對象複合而不是繼承
例如代理、裝飾、共享、門面(外觀)等,
行爲模式則是解決類之間調用關係(行爲)的。
在這裏插入圖片描述
notes: 這個blog會繼續更新,補充如果不用設計模式如何實現和設計模式對應的真實世界.
1創建者型模式
1.1 單例模式 難度* 使用*****
關鍵字:不許new,static唯一
分成兩種餓漢式單例
1.1.1 餓漢式單例和懶漢式單例

public class MySingleton {
    private static MySingleton instance = new MySingleton();
    private MySingleton(){}
    public static MySingleton getInstance() {
        return instance;
    }
}

1.1.2 懶漢式單例

public class MySingleton {
    private static MySingleton instance = null;
    private MySingleton(){}
    public static MySingleton getInstance() {
        if(instance == null){//懶漢式
            instance = new MySingleton();
        }
        return instance;
    }
}

notes:餓漢式單例多線程沒有問題. 懶漢式單例因爲 if(instance == null)多線程會錯誤,兩種解決方案,1)加同步鎖缺點效率低 2 ) 加雙重保護,詳見https://zhuanlan.zhihu.com/p/114561062
1.2 3種工廠模式
1.2.1 簡單工廠模式 難度係數 * 應用係數***
定義:根據傳遞的參數不同,返回不同類的實例。通俗的說就是switch type,然後 new一下模板模式.
工廠模式:分成兩個class,第一個class 是種類, 第二個class 是具體執行的操作.
第一個class就是簡單工廠, 兩者是1對1的關係。
關鍵字:factory(type)。 按照一個類即種類劃分工廠.
應用場景: 同一個抽象類的子類. 根據type不同,new 不同對象.
關鍵代碼:

/* * 專門用於創建披薩的工廠類 */
public class SimplePizzaFactory {
    public Pizza createPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }
        else if(type.equals("clam")){
            pizza = new ClamPizza();
        }
        else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        return pizza;
    }
}

1.2.2 工廠模式 難度係數 ** 應用係數***
定義: 工廠模式又叫做虛擬構造子(Virtual Constructor)模式或者多態性工廠(Polymorphic Factory)模式。
工廠方法模式的用意是定義一個創建產品對象的工廠接口,將實際創建工作推遲到子類中。
通俗的說工廠模式就是多態。C + +中的工廠方法都是虛函數並且常常是純虛函數
關鍵字:factory 多態
關鍵代碼

public class NYStyleCheesePizza extends Pizza{
...
}
public class NYStyleCheesePizza extends Pizza{
...
}

1.2.3 抽象工廠模式 難度係數 *** 應用係數***
可以看完策略模式之後,在看一遍抽象工廠模式
抽象工廠class is N*N 關係. 記得下面的uml依賴關係(每個class1使用自己的class2)就記得抽象工廠了.
在這裏插入圖片描述
2種class,比如多種工廠和多種產品.比如 intel computer ,amd computer(computer由cpu,內存組成)
關鍵代碼

private void prepareHardwares(int cpuType , int mainboard){
        //這裏要去準備CPU和主板的具體實現,爲了示例簡單,這裏只准備這兩個
        //可是,裝機工程師並不知道如何去創建,怎麼辦呢?       
        //直接找相應的工廠獲取
        this.cpu = CpuFactory.createCpu(cpuType);
        this.mainboard = MainboardFactory.createMainboard(mainboard); 
        //測試配件是否好用
        this.cpu.calculate();
        this.mainboard.installCPU();
}
public class CpuFactory {
    public static Cpu createCpu(int type){
        Cpu cpu = null;
        if(type == 1){
            cpu = new IntelCpu(755);
        }else if(type == 2){
            cpu = new AmdCpu(938);
        }
        return cpu;
    }
}
public class MainboardFactory {
    public static Mainboard createMainboard(int type){
        Mainboard mainboard = null;
        if(type == 1){
            mainboard = new IntelMainboard(755);
        }else if(type == 2){
            mainboard = new AmdMainboard(938);
        }
        return mainboard;
    }
}

沒有采用簡單工廠和工廠模式的類似例子,因爲抽象工廠uml圖不直觀。
https://www.cnblogs.com/chenssy/archive/2013/06/03/3114681.html
例子中芝加哥和紐約一組class1,另一組是Dough ,Sauce ,Cheese ,Veggies,Clams。應該是後者5個選幾個配置一種形成class2,與class1匹配。但是圖中採用的是細化每種原料,比如Cheese ,分成Cheese 1,Cheese 2。等於組2的子類不同形成class2,造成複雜化.
1.2.4 簡單工廠模式 vs. 工廠模式 vs. 抽象工廠模式
簡單工廠和工廠模式區別?
簡單工廠沒有多個子類extends 抽象類
工廠模式和抽象工廠模式區別?
前者1vs n,後者n vs n
1.2.5 工廠模式和其他模式的區別?
模板模式和工廠模式區別?
使用模板模式來創建實例, 就是工廠模式
繼承就是模板模式和工廠模式,
策略模式和工廠模式區別?
策略模式先set 策略後返回某個對象。工廠模式是new出來一個對象。後者是創建型,前者是行動型
建築者模式和工廠模式區別?
建造者模式是一步一步生成產品。抽象工廠是一下返回一個產品
1.3 建造者模式 難度係數 ** 應用係數***
又名生成器模式,是一種對象構建模式。將簡單的模塊,組成複雜的應用.
builder模式與一下子就生成產品的創建型模式不同,它是在導向者的控制下一步一步構造產品的。
關鍵字:builder
關鍵代碼

public interface Builder {
    public void buildPart1();
    public void buildPart2();
    public Product retrieveResult();
}

1.4 原型模式 難度係數 ** 應用係數*
原型模式:就是clone,原來的對象copy一份.clone 有兩種使用方法,方法1 .淺拷貝clone; 方法2:深拷貝.如果類有對象類型變量,需要深拷貝需要實現clone接口。
關鍵字: clone
關鍵代碼:
淺拷貝:沒有對象類型

Student student1 = new Student(1, "張三", scores);
Student student2 = student1.clone();

深拷貝: 含有對象map類型

protected Student clone()  {
    Student student = null;
    try {
        student = (Student) super.clone();
        student.scores = (Map<String, Double>) ((HashMap)this.scores).clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return student;
}

原型模式和拷貝構造函數區別?
原型模式是clone(有淺拷貝和深拷貝兩種實現方法)。拷貝構造函數是淺拷貝
2 結構型模式
2.1 適配器模式 難度係數 ** 應用係數*
真實世界場景兩項插頭轉成三項插頭.
通俗的說是外殼不換,換內容
關鍵字:adaptor
關鍵代碼:接口1調用adaptee 2

public class Adapter {
 public void sampleOperation1(){
        this.adaptee2.sampleOperation1();
    }
}

這樣做的好處是不用改成對外接口(api),外層可能有數百處Adapter.sampleOperation1 調用的地方都不用改.
第一個項目就是兩項插頭,第二個項目是三項插頭。則第二個項目僅僅修改上面的sampleOperation1中的調用即可
2.2 裝飾模式 難度係數 ** 應用係數*
裝飾者:一個對象添加一些額外的職責
裝飾者模式: 後面基於前面,加法關係不需要整體-部分關係. 比如:咖啡,加奶,加糖,加綠茶。
一層基於一層的添加,每層都是獨立應用
關鍵代碼:

DataOutputStream out = new DataOutputStream(
    new BufferedOutputStream(
      new FileOutputStream(
        new File(file))));

2.3 組合模式 難度係數 ** 應用係數*
幾個團組合一個師,幾個師組成一個軍。
組合模式:樹狀結構

public class Client {
    public static void main(String[] args) {
        // 定義多個Composite組合對象
        Component root = new Composite("服裝");
        Component c1 = new Composite("男裝");
        Component c2 = new Composite("女裝");
        // 定義多個Leaf葉子對象
        Component leaf1 = new Leaf("西服");
        Component leaf2 = new Leaf("夾克");
        Component leaf4 = new Leaf("裙子");
        Component leaf5 = new Leaf("套裝");
        // 組合成爲樹形的對象結構
        root.addChild(c1);
        root.addChild(c2);
        c1.addChild(leaf1);
        c1.addChild(leaf2);
        c2.addChild(leaf4);
        c2.addChild(leaf5);
        // 調用根對象的輸出功能輸出整棵樹
        root.someOperation("");
    }
}

2.4 外觀模式(門面模式) 難度係數 ** 應用係數*
外觀模式的名稱叫facade,類似android的中臺facade作用。
一鍵完成功能,即將幾個class封裝到一個class中.注意是class級別的封裝,不同於函數級別的封裝.
真實世界對比:小米在家中的一鍵觀電影模式,自動關窗簾,調低亮燈,打開電視和音箱等
這個設計模式僅僅是再封裝,有點濫竽充數的意思.
這個設計模式僅僅是再封裝,有點濫竽充數的意思
2.5 享元模式 ** 難度係數 ** 應用係數**
享元模式的好處是共享,比如 圍棋黑白子,如果每個棋子畫一個可能需要200多個,但是套用黑白子則僅僅兩個,只用加位置不同即可.它核心是用一個hash表key-value,有key直接取value,沒有key先save在返回value
關鍵字: hash
關鍵代碼

public class FlyweightFactory{
    private HashMap flyweights = new HashMap();
    public Flyweight getFlyweight(String key){
        if(flyweights.containsKey(key)){
            return (Flyweight)flyweights.get(key);
        }
        else{
            Flyweight fw = new ConcreteFlyweight();
            flyweights.put(key,fw);
            return fw;
        }
    }
}

享元模式和單例模式的區別?
單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
享元模式也是get value。但是它單例不同,1)單例只取一份,享元無數份 2) 單例是對象,享元是對象中的value
2.6 代理模式 難度係數 ** 應用係數**
兩種代理:靜態和動態
即不直接使用A類,保護了A的隱私性.而是在B中使用A.給外界的接口是B.A與B是一回事.B is 代理.類似封裝不直接使用變量,而是使用get函數。
動態: java 反射
關鍵字:封裝,動態 invoke
關鍵代碼

public class ProxyObject extends AbstractObject{
    RealObject realObject = new RealObject();
    @Override
    public void operation() {       
        realObject.operation();        
    }
}

2.7 橋接模式 難度係數 ** 應用係數*
記定義這個問題很難理解,但是記得下面的例子,則非常容易理解.
impl 實現分離上層和下層, 下面是linux實現 ,windows實現不care.
關鍵字:impl.
下面是網上例子的分析.
https://www.cnblogs.com/java-my-life/archive/2012/05/07/2480938.html
就是兩種類型的class分開,然後聚合nN.一種class是 種類比如sms,緊急sms;一種class是操作,實現,比如發送。
之前是用繼承實現。這種擴展兩種類都要擴展,即擴展接口及其子類。但是子類是類似的,所以需要統一.
不是按照功能單一類型class劃分,而是按照功能和實現類兩種類型class劃分,然後n
N結合.
把多層繼承,變成了單層繼承。
https://www.jianshu.com/p/61ad71954c74
即impl模式;將抽象與實現解耦,使得二者可以獨立變化
3. 行爲型模式
3.1 策略模式 難度係數 *** 應用係數**
關鍵字:strategy
一個strategy對象封裝一個算法.
兩種class.第一個class是類型,種類.第二個class統一執行接口 (計算價格或者發送,不同策略(class1)不同的算法(class2))
類似適配器模式,第二個接口不變但是內容根據第一個class的設置而變化。
主要依賴第一個class。第一個class是第二個class的if 條件.
同時第一個class 是多態的,類似工廠模式。
策略模式=工廠模式+適配器模式
調用哪個對象即set 策略,然後運行這個策略的具體執行函數
ah.setSortObj(sort); //設置具體策略
result=ah.sort(arr); 執行策略
關鍵代碼

Sort sort = new SelectionSort();    //使用選擇排序
ah.setSortObj(sort); //設置具體策略
result=ah.sort(arr);

策略模式和狀態模式?
兩者類圖一樣.
狀態/策略之間的關係:狀態模式中的不同狀態彼此相關,例如作爲前一個或者後一個狀態等。這是因爲在狀態之間像有限狀態機有一個流動。
策略模式只是從多個可用策略中選擇一個策略,策略之間沒有後者/前者的關係。
3.2 模板方法模式 難度係數 * 應用係數***
繼承模式,將各個類共同的函數,變成一個class。然後各種類繼承這個抽象類即可。
關鍵字:繼承
也是濫竽充數的模式
3.3 觀察者模式 難度係數 ** 應用係數**
觀察者模式是對象的行爲模式,又叫發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
分成兩種推模型和拉模型.
訂閱-分發(廣播)
關鍵字:observer,notify,subject
應用場景:c++ 常用的callback 是觀察者模式.比如angularjs常用.ose 系統的sebs

public interface Subject {
    public void registerObserver(Observer observer);
    public void notifyObserver();
}

3.4 狀態模式 難度係數 ** 應用係數**
• 一個state對象封裝一個與狀態相關的行爲.
關鍵字:state
應用場景:狀態機.
兩個class,一個是操作類 一個是狀態類。 兩者不是並列關係,前者使用後者(依賴關係)。
核心是第二個類,一切依賴第二個class的情況,決定了第一個class的結果。第二個class是第一個的判斷條件
將原來每種操作都要重複寫一遍各個狀態的code,將重複的code類(函數)中。
即每個操作函數,比如bookroom和checkroom的時候都調用這段重複的code即可,不用按照分支每個分支寫一遍即重複code寫了2遍.
3個狀態類,這個三個狀態分別對於這:空閒、預訂、入住。其中空閒可以完成預訂和入住兩個動作,預訂可以完成入住和退訂兩個動作,入住可以退房。

  public void bookRoom(){
        if(state == FREEMTIME_STATE){   //空閒可預訂
            if(count > 0){
                System.out.println("空閒房間,完成預訂...");
                state =  BOOKED_STATE;     //改變狀態:已預訂
                count --;           
                if(count == 0){//房間預訂完了,提示客戶沒有房源了
                    System.out.println("不好意思,房間已經預訂完,歡迎您下次光臨...");
                }
            }
            else{
                System.out.println("不好意思,已經沒有房間了....");
            }
        }
        else if(state == BOOKED_STATE){
            System.out.println("該房間已經被預訂了...");
        }
        else if(state == CHECKIN_STATE){
            System.out.println("該房間已經有人入住了...");
        }
    }

3.5 備忘錄模式 難度係數 * 應用係數*
關鍵字:save,restore
備忘錄模式: 太簡單了。就是創建一個new 對象,save。restore的時候調用save的新對象
濫竽充數的模式
3.6 迭代模式 難度係數 ** 應用係數**
關鍵字 iterator
應用場景:stl中的iterator
一個iterator對象封裝訪問和遍歷一個聚集對象中的各個構件的方法.
和其他模式不同,這個不是優化(封裝等)類的設計。這個模式是優化類的調用。
old方式調用一個class 需要while 遍歷一輪。現在將所有調用的while 變成統一(iterator遍歷(與子類通用的指針)).
傳入不同的class即可,即可全都調用
代碼1:

public class FilmMenuIterator implements Iterator{
    MenuItem[] menuItems;
    int position = 0;    
    public FilmMenuIterator(MenuItem[] menuItems){
        this.menuItems = menuItems;
    }   
public boolean hasNext() {
    if(position > menuItems.length-1 || menuItems[position] == null){
            return false;
        }
        return true;
    }
    public Object next() {
        MenuItem menuItem = menuItems[position];
        position ++;
        return menuItem;
    }
}

代碼2:

 while(iterator.hasNext()){
        MenuItem menuItem = (MenuItem) iterator.next();
   }

3.7 命令模式 難度係數 * 應用係數***
正常使用一個class的各個函數,沒想到這也是設計模式。濫竽充數的一個模式.
應用場景:撤銷命令,menu菜單
3.8 職責鏈模式 難度係數 ** 應用係數*
擊鼓傳花的順序.每層處理處理方法一樣,每次根據權限處理一些scenario
關鍵字:遞歸。
情況1:一層調用一層,每層處理一種case,如果處理不了交給下一層處理
但是覺得這種情況完成可以一層,不同的if-case處理,沒有必要一層一層傳遞。
情況2:雖然各層函數名稱一樣,但是內容不一樣,第一層if一種情況,對入參處理,傳給下一層的時候傳的參數變了。所以第2層是依賴第一層的不同case的結果的。
也是每層處理一種情況。但是不是並列關係,並列關係即情況1. link 的 源碼例子是config不同,傳給下一層。這個有用

    public void handleRequest(LeaveNode LeaveNode) {
        if(LeaveNode.getNumber() <= 3){   //小於3天輔導員審批
            System.out.println("輔導員" + name + "審批" +LeaveNode.getPerson() + "同學的請假條,請假天數爲" + LeaveNode.getNumber() + "天。");
        }
        else{     //否則傳遞給系主任
            if(this.successor != null){
                this.successor.handleRequest(LeaveNode);
            }
        }
    }

3.9 中介者模式 難度係數 ** 應用係數* *
關鍵字: mediator
一個Mediator對象封裝對象間的協議.class多對多類關係。
定義:如果一個系統中對象之間的聯繫呈現爲網狀結構,對象之間存在大量多對多的關係,將熬製關係及其複雜,這些對象稱之爲 “同事對象”.我們可以設置一箇中介者對象,使各個同事對象只跟中介者打交道,將複雜的網絡結構化解爲星形結構.
現實模型,n個房東對n個client,每個client 要求不同的房子,直接找中介,中介有所有房東的接口。
這樣,client就找中介即可
client和房東沒關係
關鍵代碼:

         //一個房主、一個租房者、一箇中介機構
        MediatorStructure mediator = new MediatorStructure(); 
        //房主和租房者只需要知道中介機構即可
        HouseOwner houseOwner = new HouseOwner("張三", mediator);
        Tenant tenant = new Tenant("李四", mediator);     
        //中介結構要知道房主和租房者
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);

3.10 解釋器模式 難度係數 ** 應用係數*
關鍵字:interpret
應用場景太小就適合語法解釋器
兩個關鍵字:非終結計算就是按照規則拆分,終結則是計算
關鍵代碼:

終結點:
  public boolean interpret(Context ctx) {   
        return left.interpret(ctx) && right.interpret(ctx);
    }
非終結點
  public boolean interpret(Context ctx) {    
        return value;
    }

3.11 訪問者模式 難度係數 *** 應用係數*
問題引出,每個visitor都要重複的一份element,避免這種情況,只留一份element,用訪問者模式.
訪問者模式=動態雙分派
關鍵字:accept ;動態雙分派. visitor,element,obstruct 3要素.
動態單分派即多態,靜態多分派即重載。沒法動態雙分派。
所以採用了兩次多態。第一次accept,第二次accept 調用visitor .
第一次element以visitor作爲參數,轉入element class accept.第二次visitor以element爲參數,轉入visitor class 具體執行
感覺第二次調用visitor是策略模式,等於策略模式外層加了一層accept?
元素說明:
Visitor就像我們去商場購物結賬時候的收銀員,具體的訪問者如收銀員甲,收銀員乙;
Element如同我們所要購買的商品貨物,具體的Element就是我們的商品A,商品B,
objectStructure就像一個購物車一樣.
動態雙分派:
在java中支持動態動態單分派和靜態多分派,不支持動態多分派。
靜態分派的參數類型是編程時已經確定的,不支持在運行時刻動態確定其參數類型,所以一定程度上不滿足多態的要求,
如果想要java支持動態多分派,就必須執行兩次動態的單分派,這種編程方式稱爲雙重分派.
與雙分派(多分派的一種特殊情況)相對的,是單分派的概念,在選擇方法(函數)時,只有一個參量(影響因素)決定具體是哪個方法.
比如說多態,虛函數的調用只有在運行時根據對象的具體類型才能決定調用的是哪個函數,多態屬於動態單分派,
含有多個參數的函數重載,則屬於多分派,每個參數都是一個影響因素。即多分派就是影響方法選擇的因素有多個,特別的,影響方法選擇的因素有兩個則稱爲雙分派。
訪問者模式是一種雙分派的體現,即由Element的具體子類型和Visist的具體子類型決定了Accept操作的不同功能。
https://www.cnblogs.com/chenssy/p/3339756.html
寫的最好的訪問者模式. 例子精妙.看code理解原理。 沒有生動的例子僅僅code理解起來很費事,僅僅有例子沒有code理解不了具體如何實現
訪問者模式是3個class的結合。
訪問者模式3種類(3要素),比其他多1個class。
home=obstruct,小貓小狗=element,主人,小學生,附近的農民,附近的科學家=visit
關鍵代碼

public void accept(Visitor visitor) {
        visitor.visitor(this);
}
public class Charger extends Visitor{
    public void visitor(MedicineA a) {
        System.out.println("劃價員:" + name +"給藥" + a.getName() +"劃價:" + a.getPrice());
    }
    public void visitor(MedicineB b) {
        System.out.println("劃價員:" + name +"給藥" + b.getName() +"劃價:" + b.getPrice());
    }   
}

https://segmentfault.com/a/1190000012495957
屬於難度比較大,應用很少的比較雞肋的模式.
4 設計模式原則:
(1) 單一原則,一個class實現一個功能
(2) 開閉原則: 抽象成接口,實現在具體類中
(3)多態, 里氏代換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那麼它不一定能夠使用基類對象.在程序中儘量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。
(4) 依賴注入:構造注入,設值注入(Setter注入)和接口注入
(5) 接口隔離原則:接口細分
(6) 合成複用原則:複用時要儘量使用組合/聚合關係(關聯關係),少用繼承
(7) 迪米特法則:使類與類之間保持鬆散的耦合關係;比如使用中介模式
參考:
https://www.cnblogs.com/chenssy/category/482229.html
https://www.cnblogs.com/java-my-life/default.html?page=3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章