12、享元模式(Flyweight Pattern)

1. 享元模式

1.1 简介

  享元(FlyWeight)模式顾名思义,既是轻量级的。享元即是共享元素,或者说是共享对象。如何共享对象呢?就是在检测对象产生的时候,如果产生的是同一个对象,那么直接使用已经产生的,听起来很像是单例模式,其实享元模式的内部实现就是很类似与单例模式的懒汉模式。享元的好处就是,在某些场景下可以节省内存,从而使得程序的性能得到提升。

flyweight设计模式意图是:

使用共享可以有效地支持大量细粒度对象

  Flyweight设计模式是一种结构设计模式,当需要创建一个类的很多对象时,可以使用Flyweight设计模式。应用Flyweight模式主要考虑一下几个因素:

  • 应用程序要创建的对象数量应该很大。
  • 对象创建在内存上很重要,也可能很耗时。
  • 对象属性可以分为内在属性和外在属性,对象的外在属性应该由客户端程序定义。

  应用flyweight模式,我们需要将Object属性划分为内部(intrinsic)属性和外部(extrinsic)属性。内在属性使对象唯一,而外在属性由客户端代码设置并用于执行不同的操作。

  Flyweight模式一般会和Factory模式搭配使用,一般会创建一个管理共享对象的Flyweight工厂,这样实现对象的创建、保存、返回等。

2. FlyWeight示例

  我们模拟一个搜索过程,在搜索中可以使用google,baidu,bing进行搜索,因为相同搜索引擎只需要创建一个,不需要创建多个,同时相同的搜索结果(网址)只需要返回同一个即可,所以都用到了享元模式。

搜索结果类:

public class Website {

    private String title;
    private String url;
    private long publishTimestamp;

    public Website(String title, String url, long publishTimestamp) {
        super();
        this.title = title;
        this.url = url;
        this.publishTimestamp = publishTimestamp;
    }

    public String getTitle() {
        return title;
    }

    public String getUrl() {
        return url;
    }

    public long getPublishTimestamp() {
        return publishTimestamp;
    }

    public String toString() {
        return String.format("[title:%s, url:%s, time:%d]", title, url, publishTimestamp);
    }
}

搜索结果享元类:

public class WebsiteRepository {

    private  static WebsiteRepository repo = new WebsiteRepository();

    public Map<String, Website> websites = new HashMap<String, Website>();

    public static WebsiteRepository getInstance() {
        return repo;
    }

    public Website getWebsite(String name, String url, long timestamp) {
        if (!websites.containsKey(url)) {
            Website newsite = new Website(name, url, timestamp);
            websites.put(url, newsite);
            return newsite;
        }

        return websites.get(url);
    }

    public int howmanyWebsite() {
        return websites.size();
    }
}

搜索引擎抽象类:

public abstract class SearchEngine {

    private long timeoutInMs;
    private String name;

    public SearchEngine (Long timeoutInMs, String name) {
        this.timeoutInMs = timeoutInMs;
        this.name = name;
    }

    public List<Website> search(String keyword) {
        System.out.println(name + " search engine with timeout set to "  + timeoutInMs + "start to search");
        return doSearch(keyword);
    }

    protected abstract List<Website> doSearch(String keyword);
}

** Google:**

public class Google extends SearchEngine {

    public Google(Long timeoutInMs, String name) {
        super(timeoutInMs, name);
    }

    @Override
    public List<Website> doSearch(String keyword) {
        List<Website> website = new ArrayList<Website>();
        website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title3", "url3", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title4", "url4", 1l));
        return website;
    }
}

** Bing:**

public class Bing extends SearchEngine {

    public Bing(Long timeoutInMs, String name) {
        super(timeoutInMs, name);
    }

    @Override
    public List<Website> doSearch(String keyword) {
        List<Website> website = new ArrayList<Website>();
        website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title4", "url4", 1l));
        return website;
    }
}

BaiDu:

public class Baidu extends SearchEngine {

    public Baidu(Long timeoutInMs, String name) {
        super(timeoutInMs, name);
    }

    @Override
    public List<Website> doSearch(String keyword) {
        List<Website> website = new ArrayList<Website>();
        website.add(WebsiteRepository.getInstance().getWebsite("莆田医院", "莆田医院", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title1", "url1", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title2", "url2", 1l));
        website.add(WebsiteRepository.getInstance().getWebsite("title3", "url3", 1l));
        return website;
    }
}

搜索引擎享元类:

public class SearchEngineFlyweightFactory {

    private static final Map<String, SearchEngine> engines = new ConcurrentHashMap<>();

    public static SearchEngine getEngine(String name, long timeoutInMs, Class<? extends SearchEngine> clazz) {
        String key = name + timeoutInMs;
        SearchEngine engine = null;
        if (engines.get(key) == null) {
                try {
                    Constructor<? extends SearchEngine> cons =  clazz.getConstructor(Long.class, String.class);
                    engine = cons.newInstance(timeoutInMs, name);
                } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
                        | IllegalArgumentException | InvocationTargetException e) {
                    e.printStackTrace();
                }
                SearchEngine existing = engines.put(key, engine);
                if (existing != null) {
                    engine = existing;
                }
        } else {
            engine = engines.get(key);
        }

        return engine;
    }

    public static int howManyEngine() {
        return engines.size();
    }
}

客户端调用:

public class FlyWeightPatternClient {

    public static void main(String[] args) throws SecurityException, ClassNotFoundException {
        search("baidu", 1000L, Baidu.class, "keyword");
        search("google", 1000L, Google.class, "keyword");
        search("bing", 1000L, Bing.class, "keyword");

        search("baidu", 1000L, Baidu.class, "keyword");
    }

    private static void search(String name, long timeoutInms, Class<? extends SearchEngine> clazz, String keyword) {
        SearchEngine engine = SearchEngineFlyweightFactory.getEngine(name, timeoutInms, clazz);
        List<Website> websites = engine.search(keyword);
        renderWebsite(websites);
        System.out.println(SearchEngineFlyweightFactory.howManyEngine());
        System.out.println(WebsiteRepository.getInstance().howmanyWebsite());
    }

    public static void renderWebsite(List<Website> websites) {
        System.out.println("start to render " );
        for (Website website : websites) {
            System.out.println(website.toString());
        }
    }
}

  在WebsiteRepository类中,使用了HashMap来将Website对象保存起来,这样就形成了一个DAC(有向无环图),只要websites变量不被释放,我们使用的共享单元是不会被释放的。这样就保证了Website对象数组不被释放,在使用享元模式的时候一定要特别注意这种情况,因为垃圾回收器(GC)在内存占用过多的时候被唤醒,然后清理那些被再被使用的内存,采用的方式就是DAC。

3. FlyWeight应用

FlyWeight应用:

  • jvm中的字符串常量池。相同的字符串会共享一个字符串对象;
  • 在HystrixCommandKey.Factory类会存储所有线程group相同的HystrixCommandKey
  • HystrixPropertiesFactory会存储线程熔断配置,这些都是享元模式的使用。

FlyWeight模式和Factory模式异同:

  • 二者都提供了对象工厂的功能,生成了类的实例供外部调用。
  • 但Flyweight模式的目的是使某些相似对象共用类的同一个实例以达到节省内存空间的目的。
  • 工厂模式则不强制这一点,它只是负责生成类的实例,另外,工厂模式还通过工厂的继承来生成具有继承关系的不同类的实例,而Flyweight模式不强调这一点。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355