Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> java/android 設計模式學習筆記(13)---享元模式

java/android 設計模式學習筆記(13)---享元模式

編輯:關於Android編程

這篇我們來介紹一下享元模式(Flyweight Pattern),Flyweight 代表輕量級的意思,享元模式是對象池的一種實現。享元模式用來盡可能減少內存使用量,它適合用於可能存在大量重復對象的場景,緩存可共享的對象,來達到對象共享和避免創建過多對象的效果,這樣一來就可以提升性能,避免內存移除和頻繁 GC 等。
  享元模式的一個經典使用案例是文本系統中圖形顯示所用的數據結構,一個文本系統能夠顯示的字符種類就是那麼幾十上百個,那麼就定義這麼些基礎字符對象,存儲每個字符的顯示外形和其他的格式化數據等,而不用每次都去新建對象,這樣就可以避免創建成千上萬的重復對象,大大提高對象的重用率。

特點

使用共享對象可有效地支持大量細粒度的對象。
  共享模式支持大量細粒度對象的復用,所以享元模式要求能夠共享的對象必須是細粒度對象。在了解享元模式之前我們先要了解兩個概念:內部狀態、外部狀態:

內部狀態:在享元對象內部不隨外界環境改變而改變的共享部分;外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態。由於享元模式區分了內部狀態和外部狀態,所以我們可以通過設置不同的外部狀態使得相同的對象可以具備一些不同的特性,而內部狀態設置為相同部分。在我們的程序設計過程中,我們可能會需要大量的細粒度對象,如果這些對象除了幾個參數不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程序當中的對象。如何利用享元模式呢?這裡我們只需要將他們少部分的不同的狀態當做參數移動到類實例的外部去,然後在方法調用的時候將他們傳遞過來就可以了。這裡也就說明了一點:內部狀態存儲於享元對象內部,而外部狀態則應該由客戶端來考慮。

 

享元模式與對象池模式對比

看了上面的特征會讓我們想到對象池模式,確實,對象池模式和享元模式有很多的相同點,但是他們有一個很重要的不同點:享元模式通常情況下獲取的是不可變的實例,而從對象池模式中獲取的對象通常情況下是可變的。所以使用享元模式用來避免創建多個擁有同樣狀態的對象,只創建一個並且在應用的不同地方都使用這一個實例;而對象池中的資源從使用的角度上看具有不同的狀態並且每個都需要單獨控制,但是又不想花費一定的資源區頻繁的創建和銷毀這些資源對象,畢竟他們都有相同的初始化過程。
  簡而言之,享元模式更加傾向於狀態的不可變性,而對象池模式則是狀態的可變性。

UML類圖

享元模式的 uml 類圖如下:
  這裡寫圖片描述

Flyweight:享元對象抽象基類或者接口;ConcreteFlyweight:具體的享元對象;UnsharedConcreteFlyweight:非共享具體享元類,指出那些不需要共享的Flyweight子類;FlyweightFactZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcnmjus/t1Kq5pLOno6y4utTwudzA7c/t1Kq21M/zs9i6zbS0vajP7dSqttTP86GjDQo8cD6+3bTLztLDx7/J0tTQtLP2z+3UqsSjyr21xLv5tKG0+sLro7o8YnIgLz4NCjxzdHJvbmc+Rmx5d2VpZ2h0LmNsYXNzPC9zdHJvbmc+PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> public interface Flyweight { void operation(); }

ConcreteFlyweight.class

public class ConcreteFlyweight implements Flyweight{

    private String intrinsicState;

    public ConcreteFlyweight(String state) {
        intrinsicState = state;
    }

    @Override
    public void operation() {
        Log.e("Shawn", "ConcreteFlyweight----" + intrinsicState);
    }
}

FlyweightFactory.class

public class FlyweightFactory {

    private HashMap mFlyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = mFlyweights.get(key);
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(key);
            mFlyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

測試代碼

Flyweight flyweight1 = factory.getFlyweight("a");
Flyweight flyweight2 = factory.getFlyweight("b");
Flyweight flyweight3 = factory.getFlyweight("a");
Log.e("Shawn", "flyweight1==flyweight2 : " + (flyweight1 == flyweight2));
Log.e("Shawn", "flyweight1==flyweight3 : " + (flyweight1 == flyweight3));
break;

結果

com.android.flyweightpattern E/Shawn: flyweight1==flyweight2 : false
com.android.flyweightpattern E/Shawn: flyweight1==flyweight3 : true

可以很明顯的看出 flyweight1 和 flyweight3 對象是同樣一個享元對象。

Java 中的享元模式

在 Java 中,最經典使用享元模式的案例就應該是 String 了,String 存在常量池中,也就是說一個 String 被定義之後它就被緩存到了常量池中,當其他地方要使用同樣的字符串時,則直接使用該緩存,而不會重復創建(這也就是 String 的不可變性:java/android 設計模式學習筆記(11)—原型模式)。
  比如下面的代碼:

String str1 = new String("abc");
String str2 = new String("abc");
String str3 = "abc";
String str4 = "ab" + "c";
str1 == str2; //false
str3 == str4; //true

str1 和 str2 是兩個不同的對象,這個應該顯而易見,而 str3 和 str4 由於都是使用的 String 享元池,所以他們兩個是同一個對象。

示例與源碼

我們這以一個圖形系統為例,用來畫不同顏色的圓形:
Shape.class用來定義一個圖形的基本行為:

public interface Shape {
    void draw();
}

Circle.class Shape 的實現子類,用來畫圓形:

public class Circle implements Shape{
    String color;

    public Circle(String color) {
        this.color = color;
    }

    @Override
    public void draw() {
        Log.e("Shawn", "畫了一個" + color +"的圓形");
    }
}

ShapeFactory.class 圖形享元工廠類:

public class ShapeFactory {
    private HashMap shapes = new HashMap<>();

    public Shape getShape(String color) {
        Shape shape = shapes.get(color);
        if (shape == null) {
            shape = new Circle(color);
            shapes.put(color, shape);
        }
        return shape;
    }

    public int getSize() {
        return shapes.size();
    }
}

測試代碼

Shape shape1 = factory.getShape("紅色");
shape1.draw();
Shape shape2 = factory.getShape("灰色");
shape2.draw();
Shape shape3 = factory.getShape("綠色");
shape3.draw();
Shape shape4 = factory.getShape("紅色");
shape4.draw();
Shape shape5 = factory.getShape("灰色");
shape5.draw();
Shape shape6 = factory.getShape("灰色");
shape6.draw();

Log.e("Shawn", "一共繪制了"+factory.getSize()+"中顏色的圓形");

最後運行結果
這裡寫圖片描述
從結果可以看到,同一個顏色的圖形共用一個對象,總共只創建了 3 個對象。

總結

享元模式實現比較簡單,但是它的作用在某些場景確實極其重要。它可以大大減少應用程序創建對象的數量和頻率,降低程序內存的占用,增強程序的性能,但它同時也增加了系統的復雜性,需要分離出外部狀態和內部狀態,內部狀態為不變的共享部分,存儲於享元對象內部;而外部狀態具有固化特性,應當由客戶端來負責,不應該隨著內部狀態改變而改變,否則會導致系統的邏輯混亂。
  享元模式優點:

能夠極大的減少系統中對象的個數;享元模式由於使用了外部狀態,外部狀態相對獨立,不會影響到內部狀態,所以享元模式使得享元對象能夠在不同的環境中被共享。  享元模式缺點:由於享元模式需要區分外部狀態和內部狀態,使得應用程序在某種程度上來說更加復雜化了;為了使對象可以共享,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。

 

討論

我在查閱相關書籍和網絡資料的過程中,看到有些文章會把 Android 中的 MessagePool 定義為享元模式,但是對比了對象池模式和享元模式之後,我更傾向於認為它是對象池模式,因為從上面介紹的對比來看,MessagePool 中對象池有初始化的 size,每次從 MessagePool 中去 obtain Message 對象的時候,獲取的都是一個初始對象,其中的狀態都需要去根據需求變化,而享元模式則更傾向於重用具有相同狀態的對象,這個對象著重於在應用的每個使用地方它的狀態都具有相同性,從這個原則來看就已經排除是享元模式了,不過還是個人的看法,有沒有大神指導一下,不勝感激~~~~

源碼下載

https://github.com/zhaozepeng/Design-Patterns/tree/master/FlyweightPattern

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved