1. 程式人生 > >Java設計模式(十二)之結構型模式:享元模式

Java設計模式(十二)之結構型模式:享元模式

一、定義:

享元模式,也就是說在一個系統中如果有多個相同的物件,那麼只共享一份就可以了,不必每個都去例項化一個物件。比如說一個文本系統,每個字母定一個物件,那麼大小寫字母一共就是52個,那麼就要定義52個物件。如果有一個1M的文字,那麼字母是何其的多,如果每個字母都定義一個物件那麼記憶體早就爆了。那麼如果要是每個字母都共享一個物件,那麼就大大節約了資源。

所謂享元模式就是執行共享技術有效地支援大量細粒度物件的複用。系統使用少量物件,而且這些都比較相似,狀態變化小,可以實現物件的多次複用。

共享模式是支援大量細粒度物件的複用,所以享元模式要求能夠共享的物件必須是細粒度物件。

在瞭解享元模式之前我們先要了解兩個概念:內部狀態、外部狀態。

內部狀態:在享元物件內部不隨外界環境改變而改變的共享部分。

外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態。

由於享元模式區分了內部狀態和外部狀態,所以我們可以通過設定不同的外部狀態使得相同的物件可以具備一些不同的特性,而內部狀態設定為相同部分。在我們的程式設計過程中,我們可能會需要大量的細粒度物件來表示物件,如果這些物件除了幾個引數不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程式當中的物件。如何利用享元模式呢?這裡我們只需要將他們少部分的不同的部分當做引數移動到類例項的外部去,然後再方法呼叫的時候將他們傳遞過來就可以了。這裡也就說明了一點:內部狀態儲存於享元物件內部,而外部狀態則應該由客戶端來考慮。

二、模式結構:

UML結構圖:

享元模式存在如下幾個角色:

(1)Flyweight: 抽象享元類。所有具體享元類的超類或者介面,通過這個介面,Flyweight可以接受並作用於外部專題;

(2)ConcreteFlyweight: 具體享元類。指定內部狀態,為內部狀態增加儲存空間。

(3)UnsharedConcreteFlyweight: 非共享具體享元類。指出那些不需要共享的Flyweight子類。

(4)FlyweightFactory: 享元工廠類。用來建立並管理Flyweight物件,它主要用來確保合理地共享Flyweight。

享元模式的核心在於享元工廠類,享元工廠類的作用在於負責維護一個物件儲存池,使用者需要物件時,首先從享元池中獲取,如果享元池中不存在,則建立一個新的享元物件返回給使用者,並在享元池中儲存該新增物件。

 

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;
        }
    }
}

三、模式實現:

 場景:假如我們有一個繪圖的應用程式,通過它我們可以出繪製各種各樣的形狀、顏色的圖形,那麼這裡形狀和顏色就是內部狀態了,通過享元模式我們就可以實現該屬性的共享了。如下:

首先是形狀類:Shape.java。它是抽象類,只有一個繪製圖形的抽象方法。

public abstract class Shape {
    public abstract void draw();
}

然後是繪製圓形的具體類。Circle.java:

public class Circle extends Shape{
    private String color;
    public Circle(String color){
        this.color = color;
    }
 
    public void draw() {
        System.out.println("畫了一個" + color +"的圓形");
    }
}

再是享元工廠類。FlyweightFactory:

//核心類
public class FlyweightFactory{
    static Map<String, Shape> shapes = new HashMap<String, Shape>();
    
    public static Shape getShape(String key){
        Shape shape = shapes.get(key);
        //如果shape==null,表示不存在,則新建,並且保持到共享池中
        if(shape == null){
            shape = new Circle(key);
            shapes.put(key, shape);
        }
        return shape;
    }
    
    public static int getSum(){
        return shapes.size();
    }
}

在這裡定義了一個HashMap 用來儲存各個物件,使用者需要物件時,首先從享元池中獲取,如果享元池中不存在,則建立一個新的享元物件返回給使用者,並在享元池中儲存該新增物件。

最後是客戶端程式:Client.java:

public class Client {
    public static void main(String[] args) {
        Shape shape1 = FlyweightFactory.getShape("紅色");
        shape1.draw();
        
        Shape shape2 = FlyweightFactory.getShape("灰色");
        shape2.draw();
        
        Shape shape3 = FlyweightFactory.getShape("綠色");
        shape3.draw();
        
        Shape shape4 = FlyweightFactory.getShape("紅色");
        shape4.draw();
        
        Shape shape5 = FlyweightFactory.getShape("灰色");
        shape5.draw();
        
        Shape shape6 = FlyweightFactory.getShape("灰色");
        shape6.draw();
        
        System.out.println("一共繪製了"+FlyweightFactory.getSum()+"中顏色的圓形");
    }
}

執行結果:

在Java語言中,String型別就是使用享元模式,String物件是final型別,物件一旦建立就不可改變。在Java中字串常量都是存在常量池中的,Java會確保一個字串常量在常量池中只有一個拷貝。

String a="abc",其中"abc"就是一個字串常量。

熟悉java的應該知道下面這個例子:

String a = "hello";
String b = "hello";
if(a == b)
 System.out.println("OK");
else
 System.out.println("Error");

輸出結果是:OK。可以看出if條件比較的是兩a和b的地址,也可以說是記憶體空間。  

四、享元模式的小結:

1、優點:

(1)享元模式可以極大減少系統中物件的個數;

(2)享元模式由於使用了外部狀態,外部狀態相對獨立,不會影響到內部狀態,所以享元模式使得享元物件能夠在不同的環境被共享。

2、缺點:

(1)由於享元模式需要區分外部狀態和內部狀態,使得應用程式在某種程度上來說更加複雜化了。

(2)為了使物件可以共享,享元模式需要將享元物件的狀態外部化,而讀取外部狀態使得執行時間變長。

3、適用場景:

(1)如果一個系統中存在大量的相同或者相似的物件,由於這類物件的大量使用,會造成系統記憶體的耗費,可以使用享元模式來減少系統中物件的數量。

(2)物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。

4、核心總結:

可以共享的物件,也就是說返回的同一型別的物件其實是同一例項,當客戶端要求生成一個物件時,工廠會檢測是否存在此物件的例項,如果存在那麼直接返回此物件例項,如果不存在就建立一個並儲存起來,這點有些單例模式的意思。通常工廠類會有一個集合型別的成員變數來用以儲存物件,如hashtable,vector等。在java中,資料庫連線池,執行緒池等即是用享元模式的應用。  

原部落格連結: