天天看點

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

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

【工匠若水 http://blog.csdn.net/yanbober】 閱讀前一篇《設計模式(結構型)之外觀模式(Facade Pattern)》http://blog.csdn.net/yanbober/article/details/45476527

概述

當一個軟體系統在運作時産生的對象數量太多,将導緻運作代價過高,帶來系統性能下降等問題。是以需要采用一個共享來避免大量擁有相同内容對象的開銷。在Java中,String類型就是使用了享元模式。String對象是final類型,對象一旦建立就不可改變。在Java中字元串常量都是存在常量池中的,Java會確定一個字元串常量在常量池中隻有一個拷貝。

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

核心

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

關于享元基礎:

享元對象共享的關鍵是區分了内部狀态(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!");
    }
}
           

總結一把

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

享元模式優點:

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

享元模式缺點:

  • 享元模式使得系統變得複雜,需要分離出内部狀态和外部狀态,這使得程式的邏輯複雜化。
  • 為了使對象可以共享,享元模式需要将享元對象的部分狀态外部化,而讀取外部狀态将使得運作時間變長。

【工匠若水 http://blog.csdn.net/yanbober】 繼續閱讀《設計模式(結構型)之代理模式(Proxy Pattern)》 http://blog.csdn.net/yanbober/article/details/45480965#t3