設計模式之享元模式

0x01.定義與類型

  • 定義:提供了減小對象數量從而改善應用所需的對象結構的方法,系統使用少許對象,並且這些都比較類似,狀態變化小,能夠實現對象的屢次複用。
  • 運用共享技術有效地支持大量細粒度的對象
  • 類型:結構型
  • 享元模式的兩個狀態:html

    • 內部狀態:在享元對象內部不隨外界環境改變而改變的共享部分。
    • 外部狀態:隨着環境的改變而改變,不可以共享的狀態就是外部狀態。
    • 因爲享元模式區分了內部狀態和外部狀態,因此咱們能夠經過設置不一樣的外部狀態使得相同的對象能夠具有一些不一樣的特性,而內部狀態設置爲相同部分。在咱們的程序設計過程當中,咱們可能會須要大量的細粒度對象來表示對象,若是這些對象除了幾個參數不一樣外其餘部分都相同,這個時候咱們就能夠利用享元模式來大大減小應用程序當中的對象。如何利用享元模式呢?這裏咱們只須要將他們少部分的不一樣的部分當作參數移動到類實例的外部去,而後再方法調用的時候將他們傳遞過來就能夠了。這裏也就說明了一點:內部狀態存儲於享元對象內部,而外部狀態則應該由客戶端來考慮。
  • UML類圖

clipboard.png

  • Java實現
/**
 * 享元對象接口
 */
public interface Flyweight {
    void operation(String extrinsicState);
}

/**
 * 享元對象工廠類,享元類
 */
public final class FlyweightFactory {

    /**
     * 享元類容器
     */
    private static Map<String, Flyweight> flyweights = new HashMap<>();

    public static Flyweight getFlyweight (String key) {
        if (flyweights.containsKey(key)) {
            return flyweights.get(key);
        } else {
            Flyweight flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
            return flyweight;
        }
    }

    public static int size () {
        return flyweights.size();
    }
}

/**
 * 能夠被共享的對象
 */
public class ConcreteFlyweight implements Flyweight{

    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String extrinsicState) {
        System.out.println(this.intrinsicState);
        System.out.println(extrinsicState);
    }
}


/**
 * 不被共享的對象
 */
public class UnsharedConcreteFlyweight implements Flyweight {

    private String allState;

    public UnsharedConcreteFlyweight(String allState) {
        this.allState = allState;
    }

    @Override
    public void operation(String extrinsicState) {
        System.out.println(this.allState);
        System.out.println(extrinsicState);
    }
}
  • 測試與應用類
/**
 * 應用與測試
 */
public class Test {

    public static void main(String[] args) {

        Stream.of("1", "1", "2", "2", "3").forEach(key -> {
            Flyweight flyweight = FlyweightFactory.getFlyweight(key);
            flyweight.operation("測試" + key);
        });

        System.out.println("size: " + FlyweightFactory.size());
    }
}
  • 輸出結果
1---:測試1
1---:測試1
2---:測試2
2---:測試2
3---:測試3
size: 3
  • 享元模式角色介紹java

    • Flyweight: 抽象享元類。全部具體享元類的超類或者接口,經過這個接口,Flyweight能夠接受並做用於外部專題。
    • ConcreteFlyweight: 具體享元類。指定內部狀態,爲內部狀態增長存儲空間。
    • UnsharedConcreteFlyweight: 非共享具體享元類。指出那些不須要共享的Flyweight子類。
    • FlyweightFactory: 享元工廠類。用來建立並管理Flyweight對象,它主要用來確保合理地共享Flyweight,當用戶請求一個Flyweight時,FlyweightFactory就會提供一個已經建立的Flyweight對象或者新建一個(若是不存在)。
  • 享元模式的核心在於享元工廠類,享元工廠類的做用在於提供一個用於存儲享元對象的享元池,用戶須要對象時,首先從享元池中獲取,若是享元池中不存在,則建立一個新的享元對象返回給用戶,並在享元池中保存該新增對象。

0x02.適用場景

  • 若是一個系統中存在大量的相同或者類似的對象,因爲這類對象的大量使用,會形成系統內存的耗費,可使用享元模式緩衝池來減小系統中對象的數量。
  • 對象的大部分狀態均可之外部化,能夠將這些外部狀態傳入對象中。
  • 經常應用於系統底層的開發,以便解決系統的性能問題。

0x03.優勢

  • 減小對象的建立,下降內存中對象的數量,下降系統的內存,提升效率。
  • 減小內存以外的其餘資源佔用。

0x04.缺點

  • 因爲享元模式須要區分外部狀態和內部狀態,使得應用程序在某種程度上來講更加複雜化了。
  • 爲了使對象能夠共享,享元模式須要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。
  • 須要關注內/外部狀態,關注線程安全問題。

0x05.模式樣例

假設一個公司中的每一個部門的部門經理都要進行彙報不止一次
  • Java實現
/**
 * 員工接口
 */
public interface Employee {
    void report();
}

/**
 * 員工工廠
 */
public class EmployeeFactory {

    private static final Map<String, Employee>  EMPLOYEE_MAP = new HashMap<>();

    public static Employee getManager(String department) {
        Manager manager = (Manager) EMPLOYEE_MAP.get(department);

        if (manager == null) {
            manager = new Manager(department);
            System.out.println("建立部門經理:" + department);
            String reportContent = department + "部門彙報:這次報告內容是。。。";
            manager.setReportContent(reportContent);
            System.out.println("建立報告: " + reportContent);
            EMPLOYEE_MAP.put(department, manager);
        }
        return manager;
    }
}

/**
 * 部門經理
 */
public class Manager implements Employee {
    /**
     * 內部狀態
     */
    private String title = "部門經理";

    /**
     * 外部狀態,須要在應用層引入,就是外部狀態
     */
    private String department;

    private String reportContent;

    public Manager(String department) {
        this.department = department;
    }

    public void setReportContent(String reportContent) {
        this.reportContent = reportContent;
    }

    @Override
    public void report() {
        System.out.println(reportContent);
    }

}
  • 測試與應用
/**
 * 測試與應用
 */
public class Test {

    private static final String[] departments = {"RD", "QA", "PM", "BD"};

    /**
     * 要注意線程安全的問題
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i ++) {
            String department = departments[(int) (Math.random() * departments.length)];
            Manager manager = (Manager) EmployeeFactory.getManager(department);
            manager.report();
        }

        System.out.println(EmployeeFactory.size());
    }
}
  • 輸出結果
建立部門經理:BD
建立報告: BD部門彙報:這次報告內容是。。。
BD部門彙報:這次報告內容是。。。
建立部門經理:PM
建立報告: PM部門彙報:這次報告內容是。。。
PM部門彙報:這次報告內容是。。。
建立部門經理:QA
建立報告: QA部門彙報:這次報告內容是。。。
QA部門彙報:這次報告內容是。。。
QA部門彙報:這次報告內容是。。。
QA部門彙報:這次報告內容是。。。
BD部門彙報:這次報告內容是。。。
PM部門彙報:這次報告內容是。。。
建立部門經理:RD
建立報告: RD部門彙報:這次報告內容是。。。
RD部門彙報:這次報告內容是。。。
PM部門彙報:這次報告內容是。。。
PM部門彙報:這次報告內容是。。。
4
  • UML類圖

clipboard.png

0x06.相關的設計模式

  • 享元模式和代理模式:當代理模式消耗性能比較大的時候,就能夠用享元模式
  • 享元模式和單例模式:容器單例,享元模式就是複用對象的思想。

0x07.源碼中的享元模式

  • JDK: Integer.valueOf(), --IntegerCache
  • Tomcat: GenericObjectPoolConfig, GenericKeyedPoolConfig

0x08.源碼地址

0x09.參考

相關文章
相關標籤/搜索