Java常用的幾種設計模式


  本來想寫點spring相關的東西的,想來想去,先寫點設計模式的東西吧

  什么是設計模式?套用百度百科的話解釋吧

  設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

  這么說,新人應該很難捉摸,大體的說,設計模式就是設計代碼結構,方便開發或者以后的調整,學習java,應該沒有不會常用設計模式的吧?

  設計模式遵循開閉原則,對拓展開放,對修改關閉,就是說,我們一個功能寫好使用之后,如果要為這個功能進行拓展,那盡量不要去修改它的代碼,而采用一些其他的方式去實現它,而這些其他的方式,就是設計模式。

  java常用的設計模式大致分為3類總共有二三十種吧,但是真正進行應用開發使用的不多,如果是做框架方面的,可以多了解一下,肯定是有幫助的,所以這里就只介紹應用開發常用的八種設計模式

一、工廠模式

  工廠模式應該都很熟悉,就是創建對象用的,看下面的例子:

package com.example;

public interface Mobile {
    public void play();
    public void call();
}
package com.example;

public class Android implements Mobile {

    public void play() {
        // TODO Auto-generated method stub
        System.out.println("Android play");
    }

    public void call() {
        // TODO Auto-generated method stub
        System.out.println("Android call");
    }
}

  首先,我們有個手機的接口Mobile,手機可以打電話,可以玩游戲,接着有個Android實現了手機接口,它實現了打電話玩游戲的功能

package com.example;

public class MobileFactory {
    public static Mobile create() {
        return new Android();
    }
}

  接着,我們創建一個工廠,用工廠創建Android手機類打電話,玩游戲,測試一下

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Mobile mobile = MobileFactory.create();
        mobile.call();// 打電話
        mobile.play();// 玩游戲
    }

}

  結果輸出:

  

  如果哪天業務需求變了,不使用Android手機了,要使用IPhone手機,那么我們只需用IPhone類實現Mobile接口,然后修改工廠的創建功能即可

package com.example;

public class IPhone implements Mobile {

    public void play() {
        // TODO Auto-generated method stub
        System.out.println("IPhone call");
    }

    public void call() {
        // TODO Auto-generated method stub
        System.out.println("IPhone call");
    }

}
package com.example;

public class MobileFactory {
    public static Mobile create() {
        return new IPhone();
    }
}

  然后同樣使用上面的測試得到結果

  

  使用工廠模式就是提醒我們,寫代碼的時候,盡可能的避免使用new關鍵字創建對象,因為new關鍵字意味着依賴,如果一個類到處都是使用new關鍵字實例化,那么哪天這個類不用了,要換成其它的實現類,我們就需要到處修改,這樣就太消耗時間了

  再者,我們上面的列子中,工廠MobileFactory中的創建方法是靜態方法,當然我們也可以換成一般的對象方法,只不過我們調用時需要創建對象去調用方法,但是可以在實例化工廠對象的時候往構造函數中傳入一些參數,從而可以對創建的對象進行一些配置或者改造,另外,根據面向接口開發的思想,我們也可以為這個工廠創建一個接口,需要實例化的工廠只需實現接口的就可以了

  還有,工廠可以有多個創建方法,創建方法可以帶參數,如上面的例子,如果需求中,我們Android類和IPhone都需要使用到,我們可以在create方法中傳入一個參數,用於指定需要實例化的是Android類還是IPhone類,或者直接創建兩個方法,一個用於實例化Android類,一個實例化IPhone類,如:  

package com.example;

public class MobileFactory {
    public static Mobile create(int flag) {
        if (flag == 1) {
            return new Android();
        } else {
            return new IPhone();
        }
    }
}

  或者

package com.example;

public class MobileFactory {
    public static Mobile createAndroid() {
        return new Android();
    }

    public static Mobile createIPhone() {
        return new IPhone();
    }
}

  總之,工廠模式就是提醒我,當需要實例化對象時,我們盡可能避免使用new去實例化,考慮為對象創建一個統一的來源

 二、單例模式

  單例模式,表面上很容易理解,就是說這個類型在存在期間只有一個實例,實現方法就是講構造函數私有化,從而除了類內部,其它地方均不能實例化類的對象,如:

package com.example;

public class Context {
    private static Context instance = null;

    private Context() {
    }

    public synchronized static Context getInstance() {
        if (instance == null) {
            instance = new Context();
        }
        return instance;
    }
}

  上面的Context類的構造函數被私有化了,因此外面是不能實例化它的,但是為了提供Context類的實例,這里就有了一個getInstance靜態方法,外面測試一下:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Context ctx1=Context.getInstance();
        Context ctx2=Context.getInstance();
        System.out.println(ctx1.equals(ctx2));
    }

}

  執行結果:

  

  可以看到,上面實例化得到的是同一個對象,細心的朋友可能會看到,在getInstance方法中,外面使用synchronized關鍵字,這個關鍵字的作用可以理解為上鎖,當一個線程進入方法時,其它線程就需要等待,當然,我們上面的例子里面,如果去掉這個關鍵字,也是不會報錯的,甚至在開發時寫單例,發布生產環境也可能不報錯,但總歸不是線程安全的,可能在一個線程去實例化這個唯一對象時,屬性等等還沒設置好,另外一個線程又去獲取了這個對象,然后獲取屬性,發現為空,可能就會引發異常

  另外,因為synchronized關鍵字是修飾方法的,也就是說每次只有一個線程能進這個方法里面去,哪怕是這個唯一實例生成后,這樣不免造成一些性能上不好的影響,所以我們可以這樣去實現:

package com.example;

public class Context {
    private static Context instance = null;private Context() {
    }
    
    private synchronized static void init() {
        if (instance == null) {
            instance = new Context();
        }
    }

    public static Context getInstance() {
        if (instance == null) {
            init();
        }
        return instance;
    }
}

  上面的例子中,synchronized還是修飾方法,但是已經不是對外的方法了,所以在某種程度上說,會更好一些。

三、適配器模式

   適配器主要指的是對接口方法進行適配,看下面的例子

package com.example;

public interface Phone {
    public void time();
    public void call() ;
}
package com.example;

public class Watch {
    public void time() {
        System.out.println("I am Watch:time");
    }
}

  首先,我們有一個電話接口,電話可以看時間,打電話,同時我們有一個手表類,手表只能看時間,如果我們現在要寫一個類去實現電話接口,一般需要實現它里面的所有方法,但是我們已經有了Watch類,所以我們可以將Watch類用上,具體實現如下:   

package com.example;

public class Telephone extends Watch implements Phone {

    public void call() {
        // TODO Auto-generated method stub
        System.out.println("I am Telephone:call");
    }
}

  測試一下調用:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Phone phone=new Telephone();
        phone.time();
        phone.call();
    }

}

  結果:  

  

  可以看到,這里我們只實現了Phone的call接口,但是Telephone繼承了Watch類,所以它有time方法,而且正好和Phone的time方法是同名同參數的,所以自動適配到了Phone接口的time方法,但是現實開發中,我們往往會碰到方法名或者參數不一樣,但是同樣功能的方法,我們就只能去顯示的實現Phone的time接口了:

package com.example;

public class Telephone implements Phone {

    public void call() {
        // TODO Auto-generated method stub
        System.out.println("I am Telephone:call");
    }

    public void time() {
        // TODO Auto-generated method stub
        new Watch().time();
    }
}

  另外,如果我們不想讓Telephone有父類,或者Telephone有其他的父類要繼承,因為java的單繼承遠程,我們也可以這么使用

  適配器模式可以理解為用已有的方法去實現同功能的接口,從而不至於造成同樣的功能幾個地方都有。還有,真實開發過程中,我們可以實現類和接口之間添加一些父類,這樣可以更利於我們封裝一些同功能的方法

四、策略模式

  程序里,我們經常會看到像下面  

     int result=0;
        if(flag==1){
            //做一些計算得到result
        }else if(flag==2){
            //做另外一些計算得到result
        }else{
            //做另外一些計算得到result
        }

  如果這里面每個flag要做一些動作,那么這樣的代碼很不容易理解,而且也不方便拓展。於是我們可以創建一個這些操作的接口,然后每個flag對應的操作封裝到不同實現這個接口的類型,比如:

package com.example;

public interface Calculator {
    public int exec(int a,int b);
}
package com.example;

public class PlusCalculator implements Calculator {

    public int exec(int a, int b) {
        // TODO Auto-generated method stub
        return a + b;
    }

}
package com.example;

public class MinusCalculator implements Calculator {

    public int exec(int a, int b) {
        // TODO Auto-generated method stub
        return a - b;
    }

}

  測試使用:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int flag = 0;
        Calculator calculator = null;
        if (flag == 1) {
            calculator = new PlusCalculator();
        } else {
            calculator = new MinusCalculator();
        }
        int result = calculator.exec(5, 3);
        System.out.println(result);
    }

}

  這里演示的只是簡單的加減,但是實際的業務算法一般是比較復雜,代碼量比較多的,如果不封裝,代碼會難看

  當然,我們也可以為這些實現類寫個父類,因為一般的,這些實現類都會有一些公共的邏輯  

五、裝飾模式

  裝飾模式也有人叫修飾模式,其實,裝飾模式就是對一些操作增加一些功能,比如在操作之前記錄操作日志,操作之后記錄結果,

  比如,上面我們定義了一個計算接口,並有兩個類實現了接口,加法類PlusCalculator 和減法類MinusCalculator ,正常調用,我們只有結果,到底哪兩個參數我們不知道:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Calculator plus = new AddCalculator();
        int plusResult = plus.exec(5, 3);
        System.out.println(plusResult);

        Calculator minus = new MinusCalculator();
        int minusResult = minus.exec(5, 3);
        System.out.println(minusResult);
    }

}

  

  現在我們可以創建裝飾類,用來記錄打印計算的參數:

package com.example;

public class Decorator implements Calculator {
    Calculator calculator = null;

    public Decorator(Calculator calculator) {
        this.calculator = calculator;
    }

    public int exec(int a, int b) {
        // TODO Auto-generated method stub

        System.out.println("before:a=" + a + "    b=" + b);
        int result = calculator.exec(a, b);
        System.out.println("after:save result:" + result);
        return result;
    }

}

  我們把測試改一下,執行后得到結果:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Calculator plus = new Decorator(new PlusCalculator());
        int plusResult = plus.exec(5, 3);
        System.out.println(plusResult);

        Calculator minus = new Decorator(new MinusCalculator());
        int minusResult = minus.exec(5, 3);
        System.out.println(minusResult);
    }

}

  

  這樣,我們就記錄到了參數信息,這種做法可以用法記錄操作日志和結果,簡單的說,裝飾模式就是拓展一個類型的功能,而不是去方法調用處一個個去加,如果接口的某些方法不想用了,可以拋出提示

六、代理模式

  代理模式在形式上和裝飾模式有點像,但是和裝飾模式的功能不一樣,裝飾模式更像修飾,修飾一個 接口,然后實現接口的所有類都能被修飾到,所以裝飾的對象一般都是接口,而代理模式充當的是中介者的角色,你要訪問必須通過類A去訪問類B,所以代理的對象一般都是真實的類對象,比如上面的加法類和減法類,我們可以創建他們的代理:  

package com.example;

public class PlusProxy implements Calculator {
    PlusCalculator calculator;

    public PlusProxy() {
        calculator = new PlusCalculator();
    }

    public int exec(int a, int b) {
        // TODO Auto-generated method stub
        System.out.println("before:a=" + a + "  b=" + b + "    a+b=?");
        int result = calculator.exec(a, b);
        System.out.println("after:a+b=" + result);
        return result;
    }

}
package com.example;

public class MinusProxy implements Calculator {

    MinusCalculator calculator;

    public MinusProxy() {
        calculator = new MinusCalculator();
    }

    public int exec(int a, int b) {
        // TODO Auto-generated method stub
        System.out.println("before:a=" + a + "  b=" + b + "    a-b=?");
        int result = calculator.exec(a, b);
        System.out.println("after:a-b=" + result);
        return result;
    }

}

  然后我們使用代理類去操作,而不是直接對用對應的計算類操作:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Calculator plus = new PlusProxy();
        int plusResult = plus.exec(5, 3);
        System.out.println(plusResult);

        Calculator minus = new MinusProxy();
        int minusResult = minus.exec(5, 3);
        System.out.println(minusResult);
    }

}

  結果輸出:

  

  可以這么說,裝飾模式和代理模式差不多,只是他們作用對象的區別

七、模板設計模式

  這個設計模式可以這么理解,把通用性的東西設置好,關鍵的部分放空,或者設置一個默認操作,然后使用模板是,只需要重寫關鍵部分就可以了,換成類的方式去說就是,使用一個父類設計好模板,放出一些方法供子類實現或重寫,比如:

package com.example;

public abstract class People {
    protected abstract String getName();

    protected abstract String getWord();

    public void say() {
        System.out.println(getName() + ":" + getWord());
    }
}

  我們有一個People類,它有三個方法,但是有兩個方法沒有具體實現,而是留給子類去實現,我們寫兩個類去實現它  

package com.example;

public class Teacher extends People {

    @Override
    protected String getName() {
        // TODO Auto-generated method stub
        return "老師";
    }

    @Override
    protected String getWord() {
        // TODO Auto-generated method stub
        return "上課";
    }

}
package com.example;

public class Student extends People {

    @Override
    protected String getName() {
        // TODO Auto-generated method stub
        return "學生";
    }

    @Override
    protected String getWord() {
        // TODO Auto-generated method stub
        return "老師好";
    }

}

  我們測試調用:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        People teacher=new Teacher();
        teacher.say();
        
        People student=new Student();
        student.say();

    }

}

  

八、觀察者模式

  觀察者模式有點像回調函數,有點像訂閱,就是先訂閱,當狀態發生變化時再通知,比如:

package com.example;

public interface Observer {
    public void send();
}

  首先,我們有一個觀察者接口,它有一個通知方法,就是通知訂閱對象,QQ用戶和微信用戶都可以實現這個接口

package com.example;

public class QQObserver implements Observer {

    public void send() {
        // TODO Auto-generated method stub
        System.out.println("QQ received");
    }

}
package com.example;

public class WxObserver implements Observer {

    public void send() {
        // TODO Auto-generated method stub
        System.out.println("Wx received");
    }

}

  現在,觀察者有兩種類型,QQ用戶和微信用戶,然后我們可以定義一個博客類

package com.example;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Blog {
    List<Observer> list=new ArrayList<Observer>();
    
    public void add(Observer obs) {
        list.add(obs);
    }    
    
    public void publish() {
        System.out.println("new blog published");
        
        Iterator<Observer> ite= list.iterator();
        while (ite.hasNext()) {
            Observer obs=ite.next();
            obs.send();
        }
    }
}

  博客可以通過add方法添加訂閱對象,當博客發布后,就會去通知這些訂閱的對象,我們測試一下:

package com.example;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Observer qq=new QQObserver();
        Observer wx=new WxObserver();
        
        Blog blog=new Blog();
        blog.add(qq);//qq用戶訂閱
        blog.add(wx);//微信用戶訂閱
        blog.publish();//博客發布        
    }
}

  

  

  總結:設計模式有很多種,但都是一些理解性的東西,自己動手敲敲代碼能更好的理解,每種設計模式都特定的用處,要看具體情況具體看。不要認為設計模式很麻煩就不注重它,無數的經驗告訴我們,一個好的程序設計能節省我們大量的時間和精力

 

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM