java與設計模式-享元模式

java與設計模式-享元模式

一、定義

享元模式(Flyweight Pattern) 是池技術的重要實現方式, 其定義如下: Use sharing tosupport large numbers of fine-grained objects efficiently.(使用共享對象可有效地支持大量的細粒度的對象。)

享元模式的定義爲我們提出了兩個要求: 細粒度的對象和共享對象。 我們知道分配太多的對象到應用程序中將有損程序的性能, 同時還容易造成內存溢出, 那怎麼避免呢? 就是享元模式提到的共享技術。 我們先來了解一下對象的內部狀態和外部狀態。

要求細粒度對象, 那麼不可避免地使得對象數量多且性質相近, 那我們就將這些對象的信息分爲兩個部分: 內部狀(intrinsic) 與外部狀態(extrinsic) 。

  • 內部狀態

內部狀態是對象可共享出來的信息, 存儲在享元對象內部並且不會隨環境改變而改變,如我們例子中的id、 postAddress等, 它們可以作爲一個對象的動態附加信息, 不必直接儲存在具體某個對象中, 屬於可以共享的部分。

  • 外部狀態

外部狀態是對象得以依賴的一個標記, 是隨環境改變而改變的、 不可以共享的狀態, 如我們例子中的考試科目+考試地點複合字符串, 它是一批對象的統一標識, 是唯一的一個索引值。

二、通用類圖

在這裏插入圖片描述

三、角色分析

  • Flyweight-抽象享元角色

它簡單地說就是一個產品的抽象類, 同時定義出對象的外部狀態和內部狀態的接口或實現。

  • ConcreteFlyweight-具體享元角色

具體的一個產品類, 實現抽象角色定義的業務。 該角色中需要注意的是內部狀態處理應該與環境無關, 不應該出現一個操作改變了內部狀態, 同時修改了外部狀態, 這是絕對不允許的。

  • unsharedConcreteFlyweight-不可共享的享元角色

不存在外部狀態或者安全要求(如線程安全) 不能夠使用共享技術的對象, 該對象一般不會出現在享元工廠中。

  • FlyweightFactory-享元工廠

職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得對象的方法。

享元模式的目的在於運用共享技術, 使得一些細粒度的對象可以共享, 我們的設計確實也應該這樣, 多使用細粒度的對象, 便於重用或重構。

四、經典代碼實現

抽象的享元角色

public abstract class FlyWeight {

    /**
     * 內部狀態
     */
    private String intrinsic;

    /**
     * 外部狀態
     * <p>{@code final}</p>
     */
    protected final String extrinsic;

    public FlyWeight(String extrinsic) {
        this.extrinsic = extrinsic;
    }

    /**
     * 定義業務操作
     */
    public abstract void operate();


    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

抽象享元角色一般爲抽象類, 在實際項目中, 一般是一個實現類, 它是描述一類事物的方法。 在抽象角色中, 一般需要把外部狀態和內部狀態(當然了, 可以沒有內部狀態, 只有行爲也是可以的) 定義出來, 避免子類的隨意擴展。我們再來看具體的享元角色。

具體享元角色

public class ConcreteFlyWeight01 extends FlyWeight {

    /**
     * 接受外部狀態
     * @param extrinsic 外部狀態
     */
    public ConcreteFlyWeight01(String extrinsic) {
        super(extrinsic);
    }

    /**
     * 根據外部狀態進行邏輯處理
     */
    @Override
    public void operate() {
        System.out.println("業務處理1...");
    }
    
}
public class ConcreteFlyWeight02 extends FlyWeight {

    /**
     * 接受外部狀態
     * @param extrinsic 外部狀態
     */
    public ConcreteFlyWeight02(String extrinsic) {
        super(extrinsic);
    }

    /**
     * 根據外部狀態進行邏輯處理
     */
    @Override
    public void operate() {
        System.out.println("業務處理2...");
    }
    
}

這很簡單, 實現自己的業務邏輯, 然後接收外部狀態, 以便內部業務邏輯對外部狀態的依賴。 注意, 我們在抽象享元中對外部狀態加上了final關鍵字, 防止意外產生, 什麼意外?獲得了一個外部狀態, 然後無意修改了一下, 池就混亂了!

在程序開發中, 確認只需要一次賦值的屬性則設置爲final類型, 避免無意修改導致邏輯混亂, 特別是Session級的常量或變量.

享元工廠

public class FlyWeightFactory {

    /**
     * 池容器
     */
    private static HashMap<String, FlyWeight> pool = new HashMap<>(32);

    /**
     * 享元模式工廠
     * @param extrinsic key
     * @return 享元對象
     */
    public static FlyWeight getFlyWeight(String extrinsic) {
        FlyWeight flyWeight = null;
        if (pool.containsKey(extrinsic)) {
            flyWeight = pool.get(extrinsic);
        } else {
            flyWeight = new ConcreteFlyWeight01(extrinsic);
            pool.put(extrinsic, flyWeight);
        }
        return flyWeight;
    }
}

場景類

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            String extrinsic = "key " + i;
            FlyWeight flyWeight = FlyWeightFactory.getFlyWeight(extrinsic);
            System.out.println(flyWeight);
        }
    }
}

打印結果如下:

...
...省略了很多行
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@1fb3ebeb
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@548c4f57
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@1218025c
com.gyoomi.designpattern.flyweight.demo01.ConcreteFlyWeight01@816f27d

五、享元模式的應用

5.1 享元模式的優點

享元模式是一個非常簡單的模式, 它可以大大減少應用程序創建的對象, 降低程序內存的佔用, 增強程序的性能, 但它同時也提高了系統複雜性, 需要分離出外部狀態和內部狀態, 而且外部狀態具有固化特性, 不應該隨內部狀態改變而改變, 否則導致系統的邏輯混亂。

5.2 享元模式的缺點

在一定程度上加重了系統的複雜性。

5.3 享元模式的使用場景

  • 系統中存在大量的相似對象
  • 細粒度的對象都具備較接近的外部狀態, 而且內部狀態與環境無關, 也就是說對象沒有特定身份
  • 需要緩衝池的場景

5.4 享元模式的線程安全問題

線程安全是一個老生常談的話題, 只要使用Java開發都會遇到這個問題, 我們之所以要在今天的享元模式中提到該問題, 是因爲該模式有太大的機率發生線程不安全, 爲什麼呢?

假如說我們key理解成業務上的非唯一的屬性,則池中的共享對象有可能在高併發的場景下,兩個或以上的取到同一的內部狀態的對象,三個線程同時操作一個對象,則就出現了我們所說的線程安全問題。

所以說設置的享元對象數量太少, 導致每個線程都到對象池中獲得對象, 然後都去修改其屬性, 於是就出現一些不和諧數據。只要使用Java開發,線程問題是不可避免的, 那我們怎麼去避免這個問題呢? 享元模式是讓我們使用共享技術,而Java的多線程又有如此問題, 該如何設計呢? 沒什麼可以參考的標準, 只有依靠經驗, 在需要的地方考慮一下線程安全, 在大部分的場景下都不用考慮。 我們在使用享元模式時, 對象池中的享元對象儘量多, 多到足夠滿足業務爲止。

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