­

「補課」進行時:設計模式(21)——享元模式

1. 前文匯總

「補課」進行時:設計模式系列

2. 享元模式

2.1 定義

享元模式(Flyweight Pattern)很簡單,它解決的需求也很直接,同時它也是池技術的重要實現方式,先看下它的定義:

Use sharing to support large numbers of fine-grained objects efficiently.(使用共享對象可有效地支持大量的細粒度的對象。)

2.2 通用類圖

  • Flyweight 抽象享元角色:它是一個產品的抽象類, 同時定義出對象的外部狀態和內部狀態的接口或實現。
  • ConcreteFlyweight 具體享元角色:具體的一個產品類, 實現抽象角色定義的業務。
  • unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如線程安全) 不能夠使用共享技術的對象, 該對象一般不會出現在享元工廠中。
  • FlyweightFactory 享元工廠:它的職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得對象的方法。

2.3 通用代碼

抽象享元角色:

public abstract class Flyweight {
    // 內部狀態
    private String intrinsic;
    // 外部狀態
    protected final String extrinsic;
    // 要求享元角色必須接受外部狀態
    protected Flyweight(String extrinsic) {
        this.extrinsic = extrinsic;
    }
    // 定義業務操作
    abstract void operate();

    public String getIntrinsic() {
        return intrinsic;
    }

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

具體享元角色:

public class ConcreteFlyweight1 extends Flyweight{
    protected ConcreteFlyweight1(String extrinsic) {
        super(extrinsic);
    }

    @Override
    void operate() {

    }
}

public class ConcreteFlyweight2 extends Flyweight{
    protected ConcreteFlyweight2(String extrinsic) {
        super(extrinsic);
    }

    @Override
    void operate() {

    }
}

享元工廠:

public class FlyweightFactory {
    // 定義一個池容器
    private static HashMap<String,Flyweight> pool = new HashMap<>();
    // 享元工廠
    public static Flyweight getFlyweight(String Extrinsic) {
        // 需要返回的對象
        Flyweight flyweight = null;
        // 在池中沒有該對象
        if(pool.containsKey(Extrinsic)) {
            flyweight = pool.get(Extrinsic);
        } else {
            // 根據外部狀態創建享元對象
            flyweight = new ConcreteFlyweight1(Extrinsic);
            // 放置到池中
            pool.put(Extrinsic, flyweight);
        }
        return flyweight;
    }
}

2.4 優缺點

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

3. 一個小例子

享元模式很簡單,上面的通用代碼其實就是一個很好的示例,類似於 Java 中的 String 常量池,沒有的對象創建後存在池中,若池中存在該對象則直接從池中取出。

我這裡還是再舉一個簡單的例子,比如接了我一個小型的外包項目,是做一個產品展示網站,後來他的朋友們也希望做這樣的網站,但要求都有些不同,我們當然不能直接複製粘貼再來一份,有人希望是視頻站,有人希望是圖文站等等,而且因為經費原因不能每個網站租用一個空間。

這種事情在生活中很長見,不過大多數情況都是直接 copy 一份代碼,再做做改動,但是在享元模式中,就不存在這種情況啦~~~

網站抽象類:

public abstract class WebSite {
    abstract void use();
}

具體網站類:

public class ConcreteWebSite extends WebSite {

    private String name;

    public ConcreteWebSite(String name) {
        this.name = name;
    }

    @Override
    void use() {
        System.out.println("網站分類:" + name);
    }
}

網絡工廠類:

public class WebSiteFactory {
    private HashMap<String, WebSite> pool = new HashMap<>();

    //獲得網站分類
    public WebSite getWebSiteCategory(String key) {
        if(!pool.containsKey(key)) {
            pool.put(key, new ConcreteWebSite(key));
        }
        return pool.get(key);
    }

    //獲得網站分類總數
    public int getWebSiteCount() {
        return pool.size();
    }
}

Client 客戶端:

public class Client {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();

        WebSite fx = factory.getWebSiteCategory("視頻站");
        fx.use();

        WebSite fy = factory.getWebSiteCategory("視頻站");
        fy.use();

        WebSite fz = factory.getWebSiteCategory("視頻站");
        fz.use();

        WebSite fa = factory.getWebSiteCategory("圖文站");
        fa.use();

        WebSite fb = factory.getWebSiteCategory("圖文站");
        fb.use();

        WebSite fc = factory.getWebSiteCategory("圖文站");
        fc.use();

        System.out.println("網站分類總數為:" + factory.getWebSiteCount());
    }
}

執行結果:

網站分類:視頻站
網站分類:視頻站
網站分類:視頻站
網站分類:圖文站
網站分類:圖文站
網站分類:圖文站
網站分類總數為:2

可以看出,雖然我們做了 6 個網站,但網站分類只有 2 個。

這樣基本算是實現了享元模式的共享對象的目的,但是這裡實際上沒有體現對象間的不同。

我們再加入一個用戶類:

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然後對 WebSiteConcreteWebSiteuse() 方法進行修改,添加 User 參數:

public abstract class WebSite {
    abstract void use(User user);
}

public class ConcreteWebSite extends WebSite {

    private String name;

    public ConcreteWebSite(String name) {
        this.name = name;
    }

    @Override
    void use(User user) {
        System.out.println("網站分類:" + name + " 用戶:" + user.getName());
    }
}

最後修改一下 Client 類:

public class Client {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();

        WebSite fx = factory.getWebSiteCategory("視頻站");
        fx.use(new User("tom"));

        WebSite fy = factory.getWebSiteCategory("視頻站");
        fy.use(new User("cat"));

        WebSite fz = factory.getWebSiteCategory("視頻站");
        fz.use(new User("nginx"));

        WebSite fa = factory.getWebSiteCategory("圖文站");
        fa.use(new User("apache"));

        WebSite fb = factory.getWebSiteCategory("圖文站");
        fb.use(new User("netty"));

        WebSite fc = factory.getWebSiteCategory("圖文站");
        fc.use(new User("jboss"));

        System.out.println("網站分類總數為:" + factory.getWebSiteCount());
    }
}

最終結果:

網站分類:視頻站 用戶:tom
網站分類:視頻站 用戶:cat
網站分類:視頻站 用戶:nginx
網站分類:圖文站 用戶:apache
網站分類:圖文站 用戶:netty
網站分類:圖文站 用戶:jboss
網站分類總數為:2

這樣就可以協調內部與外部狀態,哪怕接手了上千個網站的需求,只要要求相同或類似,實際開發代碼也就是分類的那幾種。