1. 程式人生 > >設計模式(結構型)之享元模式(Flyweight Pattern)

設計模式(結構型)之享元模式(Flyweight Pattern)

PS一句:最終還是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支援MarkDown語法了,牛逼啊!

概述

當一個軟體系統在執行時產生的物件數量太多,將導致執行代價過高,帶來系統性能下降等問題。所以需要採用一個共享來避免大量擁有相同內容物件的開銷。在Java中,String型別就是使用了享元模式。String物件是final型別,物件一旦建立就不可改變。在Java中字串常量都是存在常量池中的,Java會確保一個字串常量在常量池中只有一個拷貝。

這裡寫圖片描述

核心

概念: 運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用。由於享元模式要求能夠共享的物件必須是細粒度物件,因此它又稱為輕量級模式,它是一種物件結構型模式。

關於享元基礎:

享元物件共享的關鍵是區分了內部狀態(Intrinsic State)和外部狀態(Extrinsic State)。

內部狀態

儲存在享元物件內部並且不會隨環境改變而改變的狀態,內部狀態可以共享。

外部狀態

享元物件的外部狀態通常由客戶端儲存,並在享元物件被建立之後,需要使用的時候再傳入到享元物件內部。隨環境改變而改變的、不可以共享的狀態。一個外部狀態與另一個外部狀態之間是相互獨立的。

由於區分了內部狀態和外部狀態,我們可以將具有相同內部狀態的物件儲存在享元池中,享元池中的物件是可以實現共享的,需要的時候就將物件從享元池中取出,實現物件的複用。通過向取出的物件注入不同的外部狀態,可以得到一系列相似的物件,而這些物件在記憶體中實際上只儲存一份。

享元模式分類:

  • 單純享元模式
  • 複合享元模式

單純享元模式結構重要核心模組:

抽象享元角色

為具體享元角色規定了必須實現的方法,而外部狀態就是以引數的形式通過此方法傳入。在Java中可以由抽象類、介面來擔當。

具體享元角色

實現抽象角色規定的方法。如果存在內部狀態,就負責為內部狀態提供儲存空間。

享元工廠角色

負責建立和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!

客戶端角色

維護對所有享元物件的引用,而且還需要儲存對應的外部狀態。

單純享元模式和建立型的簡單工廠模式實現上非常相似,但是它的重點或者用意卻和工廠模式截然不同。工廠模式的使用主要是為了使系統不依賴於實現得細節;而在享元模式的主要目的是避免大量擁有相同內容物件的開銷。

複合享元模式結構重要核心模組:

抽象享元角色

為具體享元角色規定了必須實現的方法,而外部狀態就是以引數的形式通過此方法傳入。在Java中可以由抽象類、介面來擔當。

具體享元角色

實現抽象角色規定的方法。如果存在內部狀態,就負責為內部狀態提供儲存空間。

複合享元角色

它所代表的物件是不可以共享的,並且可以分解成為多個單純享元物件的組合。

享元工廠角色

負責建立和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!

客戶端角色

維護對所有享元物件的引用,而且還需要儲存對應的外部狀態。

使用場景

一個系統有大量相同或者相似的物件,造成記憶體的大量耗費。

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

在使用享元模式時需要維護一個儲存享元物件的享元池,而這需要耗費一定的系統資源,因此,應當在需要多次重複使用享元物件時才值得使用享元模式。

程式猿例項

單純享元模式例項:例子完全就是核心點的文字翻譯程式碼,不做過多解釋。

package yanbober.github.io;

import java.util.HashMap;
import java.util.Map;

//抽象享元角色類
interface ICustomerString {
    //外部狀態以引數的形式通過此方法傳入
    void opt(String state);
}
//具體享元角色類
class CustomerStringImpl implements ICustomerString {
    //負責為內部狀態提供儲存空間
    private Character mInnerState = null;

    public CustomerStringImpl(Character mInnerState) {
        this.mInnerState = mInnerState;
    }

    @Override
    public void opt(String state) {
        System.out.println("Inner state = "+this.mInnerState);
        System.out.println("Out state = "+state);
    }
}
//享元工廠角色類
//一般而言,享元工廠物件在整個系統中只有一個,因此也可以使用單例模式
class CustomerStringFactory {
    private Map<Character, ICustomerString> map = new HashMap<>();

    public ICustomerString factory(Character state) {
        ICustomerString cacheTemp = map.get(state);
        if (cacheTemp == null) {
            cacheTemp = new CustomerStringImpl(state);
            map.put(state, cacheTemp);
        }
        return cacheTemp;
    }
}
//客戶端
public class Main {
    public static void main(String[] args) {
        CustomerStringFactory factory = new CustomerStringFactory();
        ICustomerString customerString = factory.factory(new Character('Y'));
        customerString.opt("YanBo");

        customerString = factory.factory(new Character('B'));
        customerString.opt("Bob");

        customerString = factory.factory(new Character('Y'));
        customerString.opt("Jesse");
    }
}

執行結果:
Inner state = Y
Out state = YanBo
Inner state = B
Out state = Bob
Inner state = Y
Out state = Jesse

上邊示例結果一目瞭然可以看出來簡單享元模式的特點。

複合享元模式例項:

如下例子就是一個複合享元模式,添加了複合物件,具體如下:

package yanbober.github.io;

import java.util.*;

//抽象享元角色類
interface ICustomerString {
    //外部狀態以引數的形式通過此方法傳入
    void opt(String state);
}
//具體享元角色類
class CustomerStringImpl implements ICustomerString {
    //負責為內部狀態提供儲存空間
    private Character mInnerState = null;

    public CustomerStringImpl(Character mInnerState) {
        this.mInnerState = mInnerState;
    }

    @Override
    public void opt(String state) {
        System.out.println("Inner state = "+this.mInnerState);
        System.out.println("Out state = "+state);
    }
}
//複合享元物件
class MultipleCustomerStringImpl implements ICustomerString {
    private Map<Character, ICustomerString> map = new HashMap<>();

    public void add(Character key, ICustomerString value) {
        map.put(key, value);
    }

    @Override
    public void opt(String state) {
        ICustomerString temp;
        for (Character obj : map.keySet()) {
            temp = map.get(obj);
            temp.opt(state);
        }
    }
}
//享元工廠角色類
class CustomerStringFactory {
    //一般而言,享元工廠物件在整個系統中只有一個,因此也可以使用單例模式
    private Map<Character, ICustomerString> map = new HashMap<>();
    //上例的單純享元模式
    public ICustomerString factory(Character state) {
        ICustomerString cacheTemp = map.get(state);
        if (cacheTemp == null) {
            cacheTemp = new CustomerStringImpl(state);
            map.put(state, cacheTemp);
        }

        return cacheTemp;
    }
    //複合享元模式
    public ICustomerString factory(List<Character> states) {
        MultipleCustomerStringImpl impl = new MultipleCustomerStringImpl();
        for (Character state : states) {
            impl.add(state, this.factory(state));
        }

        return impl;
    }
}
//客戶端
public class Main {
    public static void main(String[] args) {
        List<Character> states = new ArrayList<>();
        states.add('Y');
        states.add('A');
        states.add('N');
        states.add('B');
        states.add('O');

        states.add('Y');
        states.add('B');

        CustomerStringFactory factory = new CustomerStringFactory();
        ICustomerString customerString1 = factory.factory(states);
        ICustomerString customerString2 = factory.factory(states);

        customerString1.opt("Mutex object test!");
    }
}

總結一把

從上面程式碼你可以發現,由於享元模式的複雜,實際應用也不是很多,這是我們更加無法看清他的真面目了。不過享元模式並不是雞肋,它的精髓是共享,是對我們系統優化非常有好處的,而且這種思想已經別越來越多的應用,這應該就算是享元模式的應用了吧。

享元模式優點:

  • 可以極大減少記憶體中物件的數量,使得相同或相似物件在記憶體中只儲存一份,從而可以節約系統資源,提高系統性能。
  • 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元物件可以在不同的環境中被共享。

享元模式缺點:

  • 享元模式使得系統變得複雜,需要分離出內部狀態和外部狀態,這使得程式的邏輯複雜化。
  • 為了使物件可以共享,享元模式需要將享元物件的部分狀態外部化,而讀取外部狀態將使得執行時間變長。