淺談java設計模式

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

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加載時互斥的),也減少了內存消耗,推薦使用內部類方式。

模式2:工廠模式

基本概念:爲創建對象提供過渡接口,以便將創建對象的具體過程屏蔽隔離起來,達到提高靈活性的目的。

分爲三類:

  • 簡單工廠模式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); //返回取值爲100Integer對象
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. 抽象工廠模式提供一個創建一系列相關或相互依賴對象的接口,而無須指定他們具體的類。它針對的是有多個產品的等級結構。而工廠方法模式針對的是一個產品的等級結構。

模式3:建造(Builder)模式

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

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

UML結構圖:


上圖是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類使之能診斷"斷肢"斷在哪個部件上,再修復這個部件。

模式4:觀察者模式

基本概念:觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。觀察者模式又叫發佈-訂閱(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  

總結

觀察者模式何時適用?

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

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

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

模式5:適配器(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接口的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。

模式6:代理模式

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

爲什麼要使用

  • 授權機制不同級別的用戶對同一對象擁有不同的訪問權利,如在論壇系統中,就使用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模式進行系統設計往往會產生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同

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