1. 程式人生 > >簡單工廠模式、單例模式以及MVC模式

簡單工廠模式、單例模式以及MVC模式

本文是對簡單工廠模式、單例模式、以及MVC模式的簡單介紹。

至於你問,為什麼把這三種模式放在一起講,筆者目前也很懵逼,因為這是一個作業...

好了,言歸正傳。

一、簡單工廠模式(Simple Factory)


嚴格的說,在23種設計模式中是沒有簡單工廠模式的,那麼它的定位是什麼呢?

在23種設計模式中,有工廠方法模式和抽象工廠模式,而一般我們提到工廠模式卻有三種,再加上簡單工廠模式。因為,GOF在《設計模式》一書中,將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory),而簡單工廠模式被看做工廠方法模式的一種特例,將兩者歸為一類。
so,我們先從最簡單的工廠模式——簡單工廠模式說起。

1.1 模式動機


舉個簡單的例子,在windows的畫圖中,可以畫圓、矩形、三角形等形狀,它們都來自同一個父類,只不過在子類修改重寫了具體的方法,使它們可以呈現不同的效果。如果我們希望,在實現這些形狀時,不需要知道具體的形狀,只需要表示該按鈕的一個引數,並提供一個方便呼叫不同形狀的方法,把引數傳入該方法即可返回對應的形狀物件,此時,需要用到簡單工廠模式了。


1.2 模式結構



簡單工廠模式包含如下角色:

1. 工廠(Factory):工廠負責實現建立所有例項的內部邏輯。
2. 抽象產品(Product):抽象產品所建立的所有物件的父類,負責所有例項所共有的公共介面。
3. 具體產品(ConcreteProduct):具體產品是建立目標,所有建立的物件都是其的具體類例項。


1.3 例項


此處,還是以形狀舉例。


1. 抽象產品類

public class Shape{
    public paint(){}
}
2. 具體產品類
public class Circle extends Shape{
    public paint(){
        System.out.println("畫了一個圓形");
    }
}

public class Rectangle extends Shape{
    public paint(){
        System.out.println("畫了一個三角形");
    }
}
3. 工廠類
public class Factory{
    public static Shape getShape(String arg){
        if(arg.equals("circle")){
            return new Circle();
        } 
        if(arg.equals("rectangle")){
            return new Rectangle();
        }
    }
}
4. 客戶端
public static void main(String[] args) {
    Shape shape = Factory.getShape("circle");
    shape.paint();
}

二、單例模式(Singleton)

單例模式,顧名思義就是一個類只有一個例項。那麼我們什麼時候會需要用到這種模式呢?

1.1 模式動機

一個簡單的例子,windows系統中的工作管理員。當我們多次點選“啟動工作管理員”時,系統始終只能彈出一個工作管理員視窗,也就是說在windows系統中,工作管理員具有唯一性。為什麼要這樣設計呢?可以從以下兩個方面考慮:1.如果能彈出多個視窗,而這些視窗都是一致的,全部都是重複的物件,勢必會浪費大量系統資源,包括cpu資源及記憶體資源,浪費是可恥的! 2.如果彈出的多個視窗內容不一致,問題就更嚴重了,使用者到底相信哪一個是真的呢?這不是在逗使用者玩嘛~因此更加不可取。  由此可見,確保工作管理員只有一個非常重要。這也顯示了單例模式的重要性。

1.2 模式定義

單例模式,顧名思義就是類只有一個例項,另外,單例模式需要滿足自行例項化並向整個系統提供這個例項。 單例模式有如下三個特點: 1.單例類只能有一個例項。 2.單例類必須自己建立自己唯一的例項。 3.單例類必須給所有其他物件提供這一例項。

接下來介紹一下單例類的兩種不同的實現,餓漢式單例和懶漢式單例。

1.3 餓漢式單例

餓漢式單例是實現起來最簡單的單例。

public class EagerSingleton{
    private EagerSingleton(){}

    private static final EagerSingleton single = new EagerSingleton();

    public static EagerSingleton getInstance(){
        return single;
    }
}

當類被載入時,靜態變數instance會被初始化,單例類的唯一例項被建立,且以後不再改變,天生是執行緒安全的。

1.4 懶漢式單例

public class LazySingleton{
    private LazySingleton(){}

    private static LazySingleton single = null;

    public static LazySingleton getInstance(){
        if(single == null){
            single = new LazySingleton();
        } 
        return single;
    }
}

懶漢式在類載入時並不自行例項化,而是在第一次呼叫getInstance() 方法時例項化,這種技術又稱為延時載入(Lazy Loader)技術,即需要的時候再載入例項。

但以上懶漢式單例並沒有考慮執行緒安全問題,它是執行緒不安全的,在併發環境下很可能出現多個例項。要實現執行緒安全,有以下三種方式(下方程式碼只對getInstance函式重寫,其他部分相同):

1.加上synchronized

synchronized public static LazySingleton getInstance(){
    if(single == null){
        single = new LazySingleton();
    } 
    return single;
}
此方法雖然實現了執行緒安全,但是每次呼叫getInstance() 時都要進行執行緒鎖定判斷,在多執行緒高併發訪問環境中,將會導致系統性能大大降低。那麼如何既能解決執行緒安全又能不影響系統性能呢?我們對此方法再次進行改進。

事實上,只需要建立例項的程式碼進行執行緒鎖定即可,程式碼如下:

public static LazySingleton getInstance(){
    if(single == null){
        synchronized (LazySingleton.class){
            single = new LazySingleton();
        }
    } 
    return single;
}

問題好像被解決了,然而並沒有...還是會存在單例物件不唯一的情況。

假如在某一瞬間,執行緒A和B都在呼叫getInstance() 方法,此時single為null,因此都可以進入if語句。由於實現了synchronized,保證只有一個執行緒在執行該程式碼,因此執行緒A繼續執行,而執行緒B進入等待狀態。當A執行完畢後,執行緒B又再一次建立了新的例項,導致例項不唯一,違背了單例模式的設計思想。

因此需要再次改進,在進入synchronized後再進行一次判斷single是否為空,這種方式稱為"雙重檢查鎖定"(Double Check Lock, DCL)。

2. 雙重檢查鎖定(DCL)

public class LazySingleton{
    private LazySingleton(){}

    private static volatile LazySingleton single = null;

    public static LazySingleton getInstance(){
        if(single == null){
            synchronized (LazySingleton.class){
                if(single == null){
                    single = new LazySingleton();   //① 非原子操作
                }
            }
        } 
        return single;
    }
}

注意,此處需要在變數single前加上修飾符volatile,否則,由於語句①是非原子操作,仍存線上程不安全的問題。而volatile的可見性及禁止指令的重排序優化的作用,可以避免此情況。另外需要注意的是,java 5 以前的版本,使用了volatile的雙重鎖還是有問題的,因為那時的volatile不能完全避免重排序。

於是,又出現了一種新的方法,既能實現實現延遲載入,而且又是執行緒安全的。

3. 靜態內部類(static nested class)

比較推薦靜態內部類的方法,這也是jdk的設計師寫的《Effective java》中推薦的。

public class LazySingleton{
    private LazySingleton(){}

    private static class Nested{
        private static final LazySingleton single = new LazySingleton();
    }

    public static LazySingleton getInstance(){
        return Nested.single;
    }
}

由於Nested是私有的,只有getInstance() 可以訪問,且在getInstance() 被第一次呼叫時才建立例項,因此它是懶漢式的,且執行緒安全,沒有效能缺陷。

三、MVC模式

3.1 模式動機

MVC模式是為了那些需要為多個檢視提供同樣的資料的的應用程式而設計的,當一個模型能為多個檢視提供資料,應用於模型的程式碼只要寫一次就夠了,大大降低程式碼的重複性。

3.1 模式介紹

MVC是Model-view-Controller(模型-檢視-控制器)的縮寫。它將應用程式分成三個核心部件,即模型、檢視、控制器,它們各自處理自己的任務,使得應用程式的輸入、處理和輸出分開。

MVC的邏輯關係


(圖片來自百度百科)

1. 檢視(View):

使用者看到並與之互動的介面。MVC的好處就是能為應用程式處理多個不同的檢視,但是檢視中並不對使用者的操作進行處理,它只是輸出資料並允許使用者操作的介面。

2. 模型(Model):

模型是MVC的核心,程式需要的資料以及程式的功能都由模型提供,因此有時又叫做資料層。

3. 控制器(Controller):

接受使用者的輸入並呼叫模型和檢視去完成使用者的請求。控制器本身不輸出任何資訊,也不做任何操作,它只是請求資訊並覺得呼叫哪個模式進行處理,然後再決定哪個檢視來顯示結果。

3.3 例項

1. 建立模型

public class Rectangle{

    private double wide;
    private double high;
    private double area;

    public void setWide(double w){
        this.wide = w;
    }

    public double getWide(){
        return wide;
    }

    public void setHigh(double h){
        this.high = h;
    }

    public double getHigh(){
        return high;
    }

    public double getArea(){
        return high*wide;
    }
}


2. 建立檢視

public class RecView{

    //view 1
    public void printHighWide(double high,double wide){
        System.out.println("Rectangle's high is :" + high);
        System.out.println("Rectangle's wide is :" + wide);
    }

    //view2
    public void printArea(double high,double wide,double area){
        System.out.println("when Rectangle's high=" + high +" and wide=" + wide);
        System.out.println("Rectangle's area is :" + area);
    }
}

3. 建立控制器

public class RecController{


    private Rectangle model;
    private RecView view;

    public RecController(Rectangle model,RecView view){
        this.model = model;
        this.view = view;
    }

    //控制器接收請求並決定所要呼叫的模型
    public void setRecHigh(double high){
        model.setHigh(high);
    }

    public void setRecWide(double wide){
        model.setWide(wide);
    }

    //由控制器決定用哪個檢視來顯示返回資料
    public void printRecHW(){
        view.printHighWide(model.getHigh(),model.getWide());
    }

    public void printRecArea(){
        view.printArea(model.getHigh(),model.getWide(),model.getArea());
    }
}

4. 用法演示

public class MVCDemo{

    private static Rectangle setData(){
        Rectangle rec = new Rectangle();
        rec.setHigh(3);
        rec.setWide(2);
        return rec;
    }

    public static void main(String[] args) {

        Rectangle model = setData();
        RecView view = new RecView();

        RecController controller = new RecController(model,view);

        //print high and wide
        controller.printRecHW();

        //updata high
        controller.setRecHigh(5);

        //print area
        controller.printRecArea();
    }

}

5.結果輸出


以上,便是本博文的全部內容。初學者,如有錯誤,歡迎指正!
(雙手合併,放於胸前,90°鞠躬)