【Java基礎】淺談常見設計模式

cryAllen發表於2016-06-29

Num1:單例模式

基本概念:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

常見寫法:

懶漢式

public class Singleton {  
    /* 持有私有靜態例項,防止被引用,此處賦值為null,目的是實現延遲載入 */  
    private static Singleton instance = null;  
  
    /* 私有構造方法,防止被例項化 */  
    private Singleton() {}  
  
    /* 1:懶漢式,靜態工程方法,建立例項 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}  

呼叫:

Singleton.getInstance().method();  

優點:延遲載入(需要的時候才去載入),適合單執行緒操作
缺點: 執行緒不安全,在多執行緒中很容易出現不同步的情況,如在資料庫物件進行的頻繁讀寫操作時。

雙重執行緒檢查模式

public class SingletonInner {  
    private static volatile SingletonInner sInst = null;  // <<< 這裡新增了 volatile  
  
    /** 
     * 私有的建構函式 
     */  
    private SingletonInner() {}  
  
    public static SingletonInner getInstance() {  
        SingletonInner inst = sInst;  // <<< 在這裡建立臨時變數
        if (inst == null) {
            synchronized (SingletonInner.class) {
                inst = sInst;
                if (inst == null) {
                    inst = new SingletonInner();
                    sInst = inst;
                }
            }
        }
        return inst;  // <<< 注意這裡返回的是臨時變數
    }
  
    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}  

呼叫:

Singleton.getInstance().method();  

優點:延遲載入,執行緒安全
缺點: 寫法複雜,不簡潔

內部類的實現

public class SingletonInner {  
    /** 
     * 內部類實現單例模式 
     * 延遲載入,減少記憶體開銷   
     */  
    private static class SingletonHolder {  
        private static SingletonInner instance = new SingletonInner();  
    }  
  
    /** 
     * 私有的建構函式 
     */  
    private SingletonInner() {}  
  
    public static SingletonInner getInstance() {  
        return SingletonHolder.instance;  
    }  
  
    protected void method() {  
        System.out.println("SingletonInner");  
    }  
}  

呼叫:

Singleton.getInstance().method();  

優點:延遲載入,執行緒安全(java中class載入時互斥的),也減少了記憶體消耗,推薦使用內部類方式。

Num2:工廠模式

基本概念:為建立物件提供過渡介面,以便將建立物件的具體過程遮蔽隔離起來,達到提高靈活性的目的。

分為三類:

  • 簡單工廠模式Simple Factory:不利於產生系列產品;

  • 工廠方法模式Factory Method:又稱為多形性工廠;

  • 抽象工廠模式Abstract Factory:又稱為工具箱,產生產品族,但不利於產生新的產品;

這三種模式從上到下逐步抽象,並且更具一般性。GOF在《設計模式》一書中將工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory)。將簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。

簡單工廠模式

簡單工廠模式又稱靜態工廠方法模式。重新命名上就可以看出這個模式一定很簡單。它存在的目的很簡單:定義一個用於建立物件的介面。

在簡單工廠模式中,一個工廠類處於對產品類例項化呼叫的中心位置上,它決定那一個產品類應當被例項化, 如同一個交通警察站在來往的車輛流中,決定放行那一個方向的車輛向那一個方向流動一樣。

先來看看它的組成:

  • 工廠類角色:這是本模式的核心,含有一定的商業邏輯和判斷邏輯。在java中它往往由一個具體類實現。
  • 抽象產品角色:它一般是具體產品繼承的父類或者實現的介面。在java中由介面或者抽象類來實現。
  • 具體產品角色:工廠類所建立的物件就是此角色的例項。在java中由一個具體類實現。

示例程式碼:

public class Factory{ //getClass 產生Sample 一般可使用動態類裝載裝入類。
    public static Sample creator(int which){ 
        if (which==1)
            return new SampleA();
        else if (which==2)
            return new SampleB();
    }
}

還有一種目前比較流行的規範是把靜態工廠方法命名為valueOf或者getInstance

valueOf:該方法返回的例項與它的引數具有同樣的值,例如:

Integer a=Integer.valueOf(100); //返回取值為100的Integer物件
public class Complex {
    private final float re;
    private final float im;

    private Complex(float re, float im){
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(float re, float im){
        return new Complex(re, im);
    }

    public static Complex valueOfPolar(float r, float theta){
        return new Complex((float)(r * Math.cos(theta)), (float)(r * Math.sin(theta)));
    }
}

從上面程式碼可以看出,valueOf()方法能執行型別轉換操作,在本例中,把int型別的基本資料轉換為Integer物件。

getInstance:返回的例項與引數匹配,例如:

Calendar cal=Calendar.getInstance(Locale.CHINA); //返回符合中國標準的日曆

工廠方法模式

工廠方法模式是簡單工廠模式的進一步抽象化和推廣,工廠方法模式裡不再只由一個工廠類決定那一個產品類應當被例項化,這個決定被交給抽象工廠的子類去做。
來看下它的組成:

  • 抽象工廠角色: 這是工廠方法模式的核心,它與應用程式無關。是具體工廠角色必須實現的介面或者必須繼承的父類。在java中它由抽象類或者介面來實現。
  • 具體工廠角色:它含有和具體業務邏輯有關的程式碼。由應用程式呼叫以建立對應的具體產品的物件
  • 抽象產品角色:它是具體產品繼承的父類或者是實現的介面。在java中一般有抽象類或者介面來實現。
  • 具體產品角色:具體工廠角色所建立的物件就是此角色的例項。在java中由具體的類來實現。

工廠方法模式使用繼承自抽象工廠角色的多個子類來代替簡單工廠模式中的“上帝類”。正如上面所說,這樣便分擔了物件承受的壓力;而且這樣使得結構變得靈活 起來——當有新的產品(即暴發戶的汽車)產生時,只要按照抽象產品角色、抽象工廠角色提供的合同來生成,那麼就可以被客戶使用,而不必去修改任何已有的代 碼。可以看出工廠角色的結構也是符合開閉原則的!

示例程式碼:

//抽象產品角色
public interface Moveable {
    void run();
}
//具體產品角色
public class Plane implements Moveable {
    @Override
    public void run() {
        System.out.println("plane....");
    }
}
//具體產品角色
public class Broom implements Moveable {
    @Override
    public void run() {
        System.out.println("broom.....");
    }
}

//抽象工廠
public abstract class VehicleFactory {
    abstract Moveable create();
}
//具體工廠
public class PlaneFactory extends VehicleFactory{
    public Moveable create() {
        return new Plane();
    }
}
//具體工廠
public class BroomFactory extends VehicleFactory{
    public Moveable create() {
        return new Broom();
    }
}
//測試類
public class Test {
    public static void main(String[] args) {
        VehicleFactory factory = new BroomFactory();
        Moveable m = factory.create();
        m.run();
    }
}

可以看出工廠方法的加入,使得物件的數量成倍增長。當產品種類非常多時,會出現大量的與之對應的工廠物件,這不是我們所希望的。因為如果不能避免這種情 況,可以考慮使用簡單工廠模式與工廠方法模式相結合的方式來減少工廠類:即對於產品樹上類似的種類(一般是樹的葉子中互為兄弟的)使用簡單工廠模式來實 現。

簡單工廠和工廠方法模式的比較

工廠方法模式和簡單工廠模式在定義上的不同是很明顯的。工廠方法模式的核心是一個抽象工廠類,而不像簡單工廠模式, 把核心放在一個實類上。工廠方法模式可以允許很多實的工廠類從抽象工廠類繼承下來, 從而可以在實際上成為多個簡單工廠模式的綜合,從而推廣了簡單工廠模式。 
反過來講,簡單工廠模式是由工廠方法模式退化而來。設想如果我們非常確定一個系統只需要一個實的工廠類, 那麼就不妨把抽象工廠類合併到實的工廠類中去。而這樣一來,我們就退化到簡單工廠模式了。

抽象工廠模式

示例程式碼:

//抽象工廠類
public abstract class AbstractFactory {
    public abstract Vehicle createVehicle();
    public abstract Weapon createWeapon();
    public abstract Food createFood();
}
//具體工廠類,其中Food,Vehicle,Weapon是抽象類,
public class DefaultFactory extends AbstractFactory{
    @Override
    public Food createFood() {
        return new Apple();
    }
    @Override
    public Vehicle createVehicle() {
        return new Car();
    }
    @Override
    public Weapon createWeapon() {
        return new AK47();
    }
}
//測試類
public class Test {
    public static void main(String[] args) {
        AbstractFactory f = new DefaultFactory();
        Vehicle v = f.createVehicle();
        v.run();
        Weapon w = f.createWeapon();
        w.shoot();
        Food a = f.createFood();
        a.printName();
    }
}

在抽象工廠模式中,抽象產品 (AbstractProduct) 可能是一個或多個,從而構成一個或多個產品族(Product Family)。 在只有一個產品族的情況下,抽象工廠模式實際上退化到工廠方法模式。

總結

  1. 簡單工廠模式是由一個具體的類去建立其他類的例項,父類是相同的,父類是具體的。 
  2. 工廠方法模式是有一個抽象的父類定義公共介面,子類負責生成具體的物件,這樣做的目的是將類的例項化操作延遲到子類中完成。
  3. 抽象工廠模式提供一個建立一系列相關或相互依賴物件的介面,而無須指定他們具體的類。它針對的是有多個產品的等級結構。而工廠方法模式針對的是一個產品的等級結構。

Num3:建造(Builder)模式

基本概念:是一種物件構建的設計模式,它可以將複雜物件的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的物件。

Builder模式是一步一步建立一個複雜的物件,它允許使用者可以只通過指定複雜物件的型別和內容就可以構建它們。使用者不知道內部的具體構建細節。Builder模式是非常類似抽象工廠模式,細微的區別大概只有在反覆使用中才能體會到。

UML結構圖:

Builder模式

上圖是Strategy 模式的結構圖,讓我們可以進行更方便的描述:

  • Builder:為建立一個Product物件的各個部件指定抽象介面。

  • ConcreteBuilder:實現Builder的介面以構造和裝配該產品的各個部件,定義並明確它所建立的表示,提供一個檢索產品的介面

  • Director:構造一個使用Builder介面的物件。

  • Product:表示被構造的複雜物件。ConcreateBuilder建立該產品的內部表示並定義它的裝配過程。

為何使用

是為了將構建複雜物件的過程和它的部件解耦。注意:是解耦過程和部件。
因為一個複雜的物件,不但有很多大量組成部分,如汽車,有很多部件:車輪、方向盤、發動機,還有各種小零件等等,部件很多,但遠不止這些,如何將這些部件裝配成一輛汽車,這個裝配過程也很複雜(需要很好的組裝技術),Builder模式就是為了將部件和組裝過程分開。

如何使用

首先假設一個複雜物件是由多個部件組成的,Builder模式是把複雜物件的建立和部件的建立分別開來,分別用Builder類和Director類來表示。

首先,需要一個介面,它定義如何建立複雜物件的各個部件:

public interface Builder {
   //建立部件A  比如建立汽車車輪void buildPartA();
   //建立部件B 比如建立汽車方向盤void buildPartB();
   //建立部件C 比如建立汽車發動機void buildPartC();
   //返回最後組裝成品結果 (返回最後裝配好的汽車)
   //成品的組裝過程不在這裡進行,而是轉移到下面的Director類中進行.
   //從而實現瞭解耦過程和部件
    Product getResult();
}

用Director構建最後的複雜物件,而在上面Builder介面中封裝的是如何建立一個個部件(複雜物件是由這些部件組成的),也就是說Director的內容是如何將部件最後組裝成成品:

public class Director {
    private Builder builder;
    public Director( Builder builder ) {
        this.builder = builder;
   }
   // 將部件partA partB partC最後組成複雜物件
   //這裡是將車輪 方向盤和發動機組裝成汽車的過程
    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

Builder的具體實現ConcreteBuilder:

  • 通過具體完成介面Builder來構建或裝配產品的部件;
  • 定義並明確它所要建立的是什麼具體東西;
  • 提供一個可以重新獲取產品的介面。
public class ConcreteBuilder implements Builder {
 Part partA, partB, partC;
 public void buildPartA() {
  //這裡是具體如何構建
 }
 public void buildPartB() {
  //這裡是具體如何構建
 }
 public void buildPartC() {
  //這裡是具體如何構建
 }
 public Product getResult() {
  //返回最後組裝成品結果
 }
}

複雜物件:產品Product:

public interface Product { }

複雜物件的部件:

public interface Part { }

我們看看如何呼叫Builder模式:

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();

Builder模式的應用

在Java實際使用中,我們經常用到"池"(Pool)的概念,當資源提供者無法提供足夠的資源,並且這些資源需要被很多使用者反覆共享時,就需要使用池。"池"實際是一段記憶體,當池中有一些複雜的資源的"斷肢"(比如資料庫的連線池,也許有時一個連線會中斷),如果迴圈再利用這些"斷肢",將提高記憶體使用效率,提高池的效能。修改Builder模式中Director類使之能診斷"斷肢"斷在哪個部件上,再修復這個部件。

Num4:觀察者模式

基本概念:觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。觀察者模式又叫釋出-訂閱(Publish/Subscribe)模式。

UML結構圖

觀察者模式

上圖是Observer 模式的結構圖,讓我們可以進行更方便的描述:

  • Subject類:它把所有對觀察者物件的引用儲存在一個聚集裡,每個主題都可以有任何數量的觀察著。抽象主題提供一個介面,可以增加和刪除觀察著物件。

  • Observer類:抽象觀察者,為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己。

  • ConcreteSubject類:具體主題,將有關狀態存入具體觀察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。

  • ConcreteObserver類:具體觀察者,實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態相協調。

如何使用

例如:老師有電話號碼,學生需要知道老師的電話號碼以便於在合適的時候撥打,在這樣的組合中,老師就是一個被觀察者(Subject),學生就是需要知道資訊的觀察者,當老師的電話號碼發生改變時,學生得到通知,並更新相應的電話記錄。

先建立一個Subject類:

/**  
 * Subject(目標,Subject):    
 * 目標知道它的觀察者。可以有任意多個觀察者觀察同一個目標。  
 * 提供註冊和刪除觀察者物件的介面。  
 */  
public interface Subject {  
    public void attach(Observer mObserver);  
    public void detach(Observer mObserver);        
    public void notice();  
} 

建立Observer類:

/**  
 * Observer(觀察者,Observer):  
 * 為那些在目標發生改變時需要獲得通知的物件定義一個更新介面。   
 */  
public interface Observer {  
    public void update();  
}  

建立ConcreteSubject類:

/**  
 * ConcreteSubject(具體目標,Teacher)  
 * 將有關狀態存入各ConcreteObserve物件。  
 * 當他的狀態發生改變時,向他的各個觀察者發出通知。   
 */  
public class Teacher implements Subject{  
     
    private String phone;  
    private Vector students;  
    
    public Teacher(){  
        phone = "";  
        students = new Vector();  
    }  
  
    @Override  
    public void attach(Observer mObserver) {  
        students.add(mObserver);  
    }  
  
    @Override  
    public void detach(Observer mObserver) {  
        students.remove(mObserver);  
    }  
  
    @Override  
    public void notice() {  
        for(int i=0;i<students.size();i++){  
            ((Observer)students.get(i)).update();  
        }  
    }  
  
    public String getPhone() {  
        return phone;  
    }  
  
    public void setPhone(String phone) {  
        this.phone = phone;  
        notice();  
    }  
}

建立ConcreteObserver類:

/**  
 * ConcreteObserver(具體觀察者, Student):  
 * 維護一個指向ConcreteSubject物件的引用。  
 * 儲存有關狀態,這些狀態應與目標的狀態保持一致。  
 * 實現Observer的更新介面以使自身狀態與目標的狀態保持一致。  
 */  
public class Student implements Observer{  
  
    private String name;  
    private String phone;  
    private Teacher mTeacher;  
      
    public Student(String name,Teacher t){  
       this.name = name;  
       mTeacher = t;  
    }  
      
    public void show(){  
       System.out.println("Name:"+name+"\nTeacher'sphone:" + phone);  
    }  
      
    @Override  
    public void update() {  
        phone = mTeacher.getPhone();  
    }  
}  

客戶端測試:

 /**  
 * 觀察者(Observer)模式測試類   
 */  
public class ObserverClient {  
    public static void main(String[] args) {  
       Vector students = new Vector();  
       Teacher t = new Teacher();  
       for(int i= 0;i<10;i++){  
           Student st = new Student("Andy.Chen"+i,t);  
           students.add(st);  
           t.attach(st);  
       }  
         
       System.out.println("Welcome to Andy.Chen Blog!" +"\n"   
                   +"Observer Patterns." +"\n"  
                   +"-------------------------------");  
         
       t.setPhone("12345678");  
       for(int i=0;i<3;i++)  
           ((Student)students.get(i)).show();  
         
       t.setPhone("87654321");  
       for(int i=0;i<3;i++)  
           ((Student)students.get(i)).show();  
    }  
} 

程式執行結果如下:

Welcome to Andy.Chen Blog!  
Observer Patterns.  
-------------------------------  
Name:Andy.Chen0  
Teacher'sphone:12345678  
Name:Andy.Chen1  
Teacher'sphone:12345678  
Name:Andy.Chen2  
Teacher'sphone:12345678  
Name:Andy.Chen0  
Teacher'sphone:87654321  
Name:Andy.Chen1  
Teacher'sphone:87654321  
Name:Andy.Chen2  
Teacher'sphone:87654321  

總結

觀察者模式何時適用?

  • 當一個抽象模型有兩個方面,其中一個方面依賴於另一方面。將這二者封裝在獨立的物件中可以使他們各自獨立地改變和複用。

  • 當對一個物件的改變需要同時改變其它物件,而不知道具體由多少物件有待改變。

  • 當一個物件必須通知其他物件,而它又不能假定其他物件是誰,換言之,你不希望這些物件是緊密耦合的。讓耦合的雙方都依賴於抽象,而不是依賴於具體。

Num5:介面卡(Adapter)模式

基本概念:介面卡模式把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。

介面卡模式的用途

用電器做例子,膝上型電腦的插頭一般都是三相的,即除了陽極、陰極外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與膝上型電腦的電源插頭不匹配使得膝上型電腦無法使用。這時候一個三相到兩相的轉換器(介面卡)就能解決此問題,而這正像是本模式所做的事情。

介面卡模式的結構
介面卡模式有類的介面卡模式物件的介面卡模式兩種不同的形式。

類介面卡模式:

類介面卡模式

在上圖中可以看出,Adaptee類並沒有sampleOperation2()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,提供一箇中間環節,即類Adapter,把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是繼承關係,這決定了這個介面卡模式是類的:

  • 目標(Target)角色:這就是所期待得到的介面。注意:由於這裡討論的是類介面卡模式,因此目標不可以是類。
  • 源(Adapee)角色:現在需要適配的介面。
  • 介面卡(Adaper)角色:介面卡類是本模式的核心。介面卡把源介面轉換成目標介面。顯然,這一角色不可以是介面,而必須是具體類。

示例程式碼:

public interface Target {
    /**
     * 這是源類Adaptee也有的方法
     */
    public void sampleOperation1(); 
    /**
     * 這是源類Adapteee沒有的方法
     */
    public void sampleOperation2(); 
}

上面給出的是目標角色的原始碼,這個角色是以一個Java介面的形式實現的。可以看出,這個介面宣告瞭兩個方法:sampleOperation1()和sampleOperation2()。而源角色Adaptee是一個具體類,它有一個sampleOperation1()方法,但是沒有sampleOperation2()方法。

public class Adaptee {
    public void sampleOperation1(){}
}

介面卡角色Adapter擴充套件了Adaptee,同時又實現了目標(Target)介面。由於Adaptee沒有提供sampleOperation2()方法,而目標介面又要求這個方法,因此介面卡角色Adapter實現了這個方法。

public class Adapter extends Adaptee implements Target {
    /**
     * 由於源類Adaptee沒有方法sampleOperation2()
     * 因此介面卡補充上這個方法
     */
    @Override
    public void sampleOperation2() {
        //寫相關的程式碼
    }
}
物件介面卡模式:

物件介面卡

從上圖可以看出,Adaptee類並沒有sampleOperation2()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,需要提供一個包裝(Wrapper)類Adapter。這個包裝類包裝了一個Adaptee的例項,從而此包裝類能夠把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是委派關係,這決定了介面卡模式是物件的。

示例程式碼:

public interface Target {
    /**
     * 這是源類Adaptee也有的方法
     */
    public void sampleOperation1(); 
    /**
     * 這是源類Adapteee沒有的方法
     */
    public void sampleOperation2(); 
}

public class Adaptee {
    public void sampleOperation1(){}
}

介面卡類:

public class Adapter {
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    /**
     * 源類Adaptee有方法sampleOperation1
     * 因此介面卡類直接委派即可
     */
    public void sampleOperation1(){
        this.adaptee.sampleOperation1();
    }
    /**
     * 源類Adaptee沒有方法sampleOperation2
     * 因此由介面卡類需要補充此方法
     */
    public void sampleOperation2(){
        //寫相關的程式碼
    }
}

類介面卡和物件介面卡的權衡

  • 類介面卡使用物件繼承的方式,是靜態的定義方式;而物件介面卡使用物件組合的方式,是動態組合的方式。
  • 對於類介面卡由於介面卡直接繼承了Adaptee,使得介面卡不能和Adaptee的子類一起工作,因為繼承是靜態的關係,當介面卡繼承了Adaptee後,就不可能再去處理  Adaptee的子類了。

  • 對於物件介面卡一個介面卡可以把多種不同的源適配到同一個目標。換言之,同一個介面卡可以把源類和它的子類都適配到目標介面。因為物件介面卡採用的是物件組合的關係,只要物件型別正確,是不是子類都無所謂。

  • 對於類介面卡介面卡可以重定義Adaptee的部分行為,相當於子類覆蓋父類的部分實現方法。

  • 對於物件介面卡要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實現重定義,然後讓介面卡組合子類。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很,而且新增加的行為可同時適用於所有的源。

  • 對於類介面卡,僅僅引入了一個物件,並不需要額外的引用來間接得到Adaptee。

  • 對於物件介面卡,需要額外的引用來間接得到Adaptee。

建議儘量使用物件介面卡的實現方式,多用合成或聚合、少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的。

介面卡模式的優點

  • 更好的複用性:系統需要使用現有的類,而此類的介面不符合系統的需要。那麼通過介面卡模式就可以讓這些功能得到更好的複用。

  • 更好的擴充套件性:在實現介面卡功能的時候,可以呼叫自己開發的功能,從而自然地擴充套件系統的功能。

介面卡模式的缺點

  過多的使用介面卡,會讓系統非常零亂,不易整體進行把握。比如,明明看到呼叫的是A介面,其實內部被適配成了B介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用介面卡,而是直接對系統進行重構。

Num6:代理模式

基本概念:為其他物件提供一種代理以控制對這個物件的訪問。也可以說,在出發點到目的地之間有一道中間層,意為代理。

為什麼要使用

  • 授權機制不同級別的使用者對同一物件擁有不同的訪問權利,如在論壇系統中,就使用Proxy進行授權機制控制,訪問論壇有兩種人:註冊使用者和遊客(未註冊使用者),論壇就通過類似ForumProxy這樣的代理來控制這兩種使用者對論壇的訪問許可權。

  • 某個客戶端不能直接操作到某個物件,但又必須和那個物件有所互動。

舉例兩個具體情況:

  • 如果那個物件是一個是很大的圖片,需要花費很長時間才能顯示出來,那麼當這個圖片包含在文件中時,使用編輯器或瀏覽器開啟這個文件,開啟文件必須很迅速,不能等待大圖片處理完成,這時需要做個圖片Proxy來代替真正的圖片。
  • 如果那個物件在Internet的某個遠端伺服器上,直接操作這個物件因為網路速度原因可能比較慢,那我們可以先用Proxy來代替那個物件。

總之原則是,對於開銷很大的物件,只有在使用它時才建立,這個原則可以為我們節省很多寶貴的Java記憶體。所以,有些人認為Java耗費資源記憶體,我以為這和程式編制思路也有一定的關係。

如何使用

以論壇系統為例,訪問論壇系統的使用者有多種型別:註冊普通使用者、論壇管理者、系統管理者、遊客。註冊普通使用者才能發言,論壇管理者可以管理他被授權的論壇,系統管理者可以管理所有事務等,這些許可權劃分和管理是使用Proxy完成的。
在Forum中陳列了有關論壇操作的主要行為,如論壇名稱,論壇描述的獲取和修改,帖子發表刪除編輯等,在ForumPermissions中定義了各種級別許可權的使用者:

public class ForumPermissions implements Cacheable {
    /**
    * Permission to read object.
    */
    public static final int READ = 0;

    /**
    * Permission to administer the entire sytem.
    */
    public static final int SYSTEM_ADMIN = 1;

    /**
    * Permission to administer a particular forum.
    */
    public static final int FORUM_ADMIN = 2;

    /**
    * Permission to administer a particular user.
    */
    public static final int USER_ADMIN = 3;

    /**
    * Permission to administer a particular group.
    */
    public static final int GROUP_ADMIN = 4;

    /**
    * Permission to moderate threads.
    */
    public static final int MODERATE_THREADS = 5;

    /**
    * Permission to create a new thread.
    */
    public static final int CREATE_THREAD = 6;

    /**
    * Permission to create a new message.
    */
    public static final int CREATE_MESSAGE = 7;

    /**
    * Permission to moderate messages.
    */
    public static final int MODERATE_MESSAGES = 8;
  
    public boolean isSystemOrForumAdmin() {
        return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
    }

//相關操作程式碼
}

因此,Forum中各種操作許可權是和ForumPermissions定義的使用者級別有關係的,作為介面Forum的實現:ForumProxy正是將這種對應關係聯絡起來。比如,修改Forum的名稱,只有論壇管理者或系統管理者可以修改,程式碼如下:

public class ForumProxy implements Forum {
    private ForumPermissions permissions;
    private Forum forum;
    this.authorization = authorization;

    public ForumProxy(Forum forum, Authorization authorization,ForumPermissions permissions){
        this.forum = forum;
        this.authorization = authorization;
        this.permissions = permissions;
    }
    .....
    public void setName(String name) throws UnauthorizedException,
        ForumAlreadyExistsException{
        //只有是系統或論壇管理者才可以修改名稱
      if (permissions.isSystemOrForumAdmin()) {
        forum.setName(name);
      }
    else {
    throw new UnauthorizedException();
    }
    }
    ...

} 

而DbForum才是介面Forum的真正實現,以修改論壇名稱為例:

public class DbForum implements Forum, Cacheable {
    ...
    public void setName(String name) throws ForumAlreadyExistsException {
  ....
        this.name = name;
       //這裡真正將新名稱儲存到資料庫中
       saveToDb();
  ....
    }
    ...
}

凡是涉及到對論壇名稱修改這一事件,其他程式都首先得和ForumProxy打交道,由ForumProxy決定是否有許可權做某一樣事情,ForumProxy是個名副其實的"閘道器","安全代理系統"。
在平時應用中,無可避免總要涉及到系統的授權或安全體系,不管你有無意識的使用Proxy,實際你已經在使用Proxy了。

流程圖

代理模式

Num7:裝飾模式

基本概念:裝飾模式(Decorator),動態地給一個物件新增一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活。

UML結構圖

裝飾模式

上圖是Decorator 模式的結構圖,讓我們可以進行更方便的描述:

  • Component是定義一個物件介面,可以給這些物件動態地新增職責。

  • ConcreteComponent是定義了一個具體的物件,也可以給這個物件新增一些職責。

Decorator是裝飾抽象類,繼承了Component,從外類來擴充套件Component類的功能,但對於Component來說,是無需知道Decorator存在的。ConcreteDecorator就是具體的裝飾物件,起到給Component新增職責的功能。

如何使用

假設情景:某人裝扮自己形象,穿衣服,褲子,鞋子,戴帽子等來把自己給包裝起來,需要把所需的功能按正確的順序串聯起來進行控制,我們應該如何設計才能做到呢?如下,先看下程式碼結構圖:

先建立一個介面類:Component.java

public interface Component {    
    void show();
}

建立一個具體的 ConcreteComponent 來實現 Component 介面:Person.java

public class Person implements Component{
    private String name;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person(String name){
        this.name = name;
    }

    @Override
    public void show() {
        System.out.println("裝扮的" + name);
    }
}

建立裝飾類 Decorator 實現 Component 介面

public class Decorator implements Component{
    private Component mComponent;
    public void decoratorObj(Component component){
        mComponent = component;
    }

    @Override
    public void show() {
        if(mComponent != null){
            mComponent.show();
        }
    }
}

分別建立具體的裝飾類:Jeans.java , Pelisse.java, Sandal.java ...等等,分別繼承 Decorator.java 類

/** 牛仔褲 */
public class Jeans extends Decorator {
    @Override
    public void show(){
        System.out.println("穿牛仔褲");
        super.show();
    }
    
}

客戶端測試類

/**
 * 裝飾模式測試客戶端
 */
public class DecoratorClient {
    public static void main(String[] args) {
        System.out.println("Welcome to Andy.Chen Blog!" +"\n" 
                   +"Decorator Patterns." +"\n");
        
        Person mPerson = new Person("Andy");
        
        Sandal mSandal = new Sandal();
        Jeans mJeans = new Jeans();
        TShirt mShirt = new TShirt();
        
        mShirt.decoratorObj(mPerson);
        mJeans.decoratorObj(mShirt);
        mSandal.decoratorObj(mJeans);
        mSandal.show(); 
    }
}

測試結果

Welcome to Andy.Chen Blog!
Decorator Patterns.
  
穿涼鞋
穿牛仔褲
穿T-Shirt
裝扮的Andy

總結

Decorator模式有以下的優缺點:

  • 比靜態繼承更靈活與物件的靜態繼承相比,Decorator模式提供了更加靈活的向物件新增職責的方式,可以使用新增和分離的方法,用裝飾在執行時刻增加和刪除職責。使用繼承機制增加職責需要建立一個新的子類,如果需要為原來所有的子類都新增功能的話,每個子類都需要重寫,增加系統的複雜度,此外可以為一個特定的Component類提供多個Decorator,這種混合匹配是適用繼承很難做到的。

  • 避免在層次結構高層的類有太多的特徵,Decorator模式提供了一種“即用即付”的方法來新增職責,他並不試圖在一個複雜的可訂製的類中支援所有可預見的特徵,相反可以定義一個簡單的類,並且用Decorator類給他逐漸的新增功能,從簡單的部件組合出複雜的功能。

  • Decorator 與它的Component不一樣Decorator是一個透明的包裝,如果我們從物件標識的觀點出發,一個被裝飾了的元件與這個元件是有差別的,因此使用裝飾時不應該以來物件標識。

  • 產生許多小物件,採用Decorator模式進行系統設計往往會產生許多看上去類似的小物件,這些物件僅僅在他們相互連線的方式上有所不同。

參考地址:

1,http://www.cnblogs.com/forlina/archive/2011/06/21/2086114.html

2,http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html

3,http://blog.csdn.net/cjjky/article/details/7478788

4,http://blog.csdn.net/cjjky/article/details/7384951

5,http://blog.csdn.net/cjjky/article/details/7327200

相關文章