前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【进阶之路】理解结构型模式开发(享元模式)

【进阶之路】理解结构型模式开发(享元模式)

作者头像
南橘
发布2022-09-16 14:41:48
1560
发布2022-09-16 14:41:48
举报
文章被收录于专栏:进阶之路进阶之路

导言

大家好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助

首先推销一下之前关于设计模式的文章:

【进阶之路】理解结构型模式开发(桥接模式)

说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用。在之前的代码调优的过程中,我就稍微触碰了一下享元模式,但是没有具体来解释。

【进阶之路】Java代码性能调优(一)

这篇文章,我就和大家一起分享一下我对享元模式的理解。

一、常量池

首先还是从常量池来一起学习享元模式。

代码语言:javascript
复制
 	 Integer integer1=new Integer(1);
         Integer integer2=new Integer(2);
         System.out.println(integer1==integer2);	 //false
代码语言:javascript
复制
   	 Integer integer3=2;	//放入Integer常量池
   	 Integer integer4=2; //从量池获取
   	 System.out.println(integer3==integer4);	 //true
代码语言:javascript
复制
        Integer integer5=128;
        Integer integer6=128;
        System.out.println(integer5==integer6);	 //false

相信大家一眼就能看出为什么,因为像 “Integer 变量名=?” 这种形式定义的Integer变量会被放入常量池,当一个Integer变量放入常量池前会有一个判断,若常量池中存在和该变量值相等的变量,则两变量共用一块内存,否则将该变量存入变量池,单独分配内存。

而正如大家所理解的这样,IntegerCache为Integer类的缓存类,默认缓存了-128~127的Integer值,如遇到[-128,127]范围的值需要转换为Integer时才会从IntegerCache中获取

二、定义

享元模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。如果一个系统中存在多个相同的对象,那么只需共享一份对象的拷贝,而不必为每一次使用都创建新的对象。

主要优点:

代码语言:javascript
复制
相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

主要缺点:

代码语言:javascript
复制
为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
读取享元模式的外部状态会使得运行时间稍微变长。

在享元模式中可以共享的相同内容称为内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的,然后通过共享不变的部分,达到减少对象数量并节约内存的目的

  在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。

享元模式的主要角色

代码语言:javascript
复制
抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

三、实现单纯享元模式

抽象享元角色
代码语言:javascript
复制
public interface Flyweight {
    public void operate(String type);
}
具体享元+非享元
代码语言:javascript
复制
public class CharacterFlyweight implements Flyweight{
    //内部状态即是不变的,外部状态是变化的,然后通过共享不变的部分,达到减少对象数量并节约内存的目的
    private String name;

    /**
     * 外部状态(非享元)
     * @param name
     */
    public CharacterFlyweight(String name) {
        this.name = name;
    }

    /**
     * 具体享元+非享元结合
     * @param type
     */
    @Override
    public void operate(String type) {
        System.out.println("姓名 = " + name);
        System.out.println("属性 = " + type);
    }
}

享元工厂
代码语言:javascript
复制
public class FlyweightFactory {
    //由工厂方法产生所需要的享元对象。
    private Map<String,Flyweight> characterPool = new HashMap<String,Flyweight>();

    public Flyweight factory(String user){
        //先从缓存中查找对象
        Flyweight flyweight = characterPool.get(user);
        if(flyweight == null){
            //如果对象不存在则创建一个新的对象
            flyweight = new CharacterFlyweight(user);
            //新对象添加到缓存中
            characterPool.put(user, flyweight);
        }
        return flyweight;
    }
}

最后结果如下,我们的客户端申请了三个享元对象,但是实际创建的享元对象只有两个。 这个就是享元模式的意义所在。

代码语言:javascript
复制
 public static void main(String[] args) {

        FlyweightFactory factory = new FlyweightFactory();
        Flyweight flyweight1 = factory.factory("蛇夫");
        flyweight1.operate("蛇夫赶小犬");

        Flyweight flyweight2 = factory.factory("室女");
        flyweight2.operate("梦游室女座");

        Flyweight flyweight3 = factory.factory("蛇夫");
        flyweight3.operate("蛇夫逐天狼");

        System.out.println(flyweight1==flyweight3);//true
    }
    

四、实现复合享元模式

了解了单纯享元模式,我们再来了解一下复合享元模式。

将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享

抽象享元角色
代码语言:javascript
复制
public interface Flyweight {
    public void operate(String type);
}
具体享元+非享元
代码语言:javascript
复制
public class CharacterFlyweight implements Flyweight{
    //内部状态即是不变的,外部状态是变化的,然后通过共享不变的部分,达到减少对象数量并节约内存的目的
    private String name;

    /**
     * 外部状态(非享元)
     * @param name
     */
    public CharacterFlyweight(String name) {
        this.name = name;
    }

    /**
     * 具体享元+非享元结合
     * @param type
     */
    @Override
    public void operate(String type) {
        System.out.println("姓名 = " + name);
        System.out.println("属性 = " + type);
    }
}

复合享元角色类
代码语言:javascript
复制
public class CharacterCompositeFlyweight implements Flyweight{
    private Map<String,Flyweight> files = new HashMap<String,Flyweight>();
    /**
     * 增加一个新的单纯享元对象到即合理
     */
    public void add(String key , Flyweight fly){
        files.put(key,fly);
    }
    /**
     * 外部状态作为参数传入到方法中
     */

    @Override
    public void operate(String type) {
        Flyweight fly ;
        for(Object o : files.keySet()){
            fly = files.get(o);
            fly.operate(type);
        }
    }
}
享元工厂
代码语言:javascript
复制
public class FlyweightFactory {

    private Map<String,Flyweight> characterPool = new HashMap<String,Flyweight>();
    /**
     * 复合享元工厂方法
     * 一种用于提供单纯享元对象,另一种用于提供复合享元对象
     */
    public Flyweight factory(List<String> compositeState){
        CharacterCompositeFlyweight compositeFlyweight = new CharacterCompositeFlyweight();

        for(String state : compositeState){
            compositeFlyweight.add(state,this.factory(state));
        }

        return compositeFlyweight;
    }

    /**
     *单纯工厂方法产生所需要的享元对象。
     */
    public Flyweight factory(String user){
        //先从缓存中查找对象
        Flyweight flyweight = characterPool.get(user);
        if(flyweight == null){
            //如果对象不存在则创建一个新的对象
            flyweight = new CharacterFlyweight(user);
            //新对象添加到缓存中
            characterPool.put(user, flyweight);
        }
        return flyweight;
    }
}

最后结果如下,我们的客户端申请了三个享元对象,但是实际创建的享元对象只有两个。 这个就是享元模式的意义所在。

代码语言:javascript
复制
 
    public static void main(String[] args) {

        List<String> compositeState = new ArrayList<String>();
        compositeState.add("室女");
        compositeState.add("双子");
        compositeState.add("武仙");
        compositeState.add("双子");
        compositeState.add("室女");

        FlyweightFactory flyFactory = new FlyweightFactory();
        Flyweight compositeFly1 = flyFactory.factory(compositeState);
        Flyweight compositeFly2 = flyFactory.factory(compositeState);
        compositeFly1.operate("梦游中...");

        System.out.println("---------------------------------");
        System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2)); //false

        String user = "室女";
        Flyweight fly1 = flyFactory.factory(user);
        Flyweight fly2 = flyFactory.factory(user);
        System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2)); //true
    }

跑完测试类之后,我们能够明显地看出,复合享元模式也拥有单存享元的一切特征,但是它本身不能共享,只能被分解成单存享元来共享。

四、享元模式的应用

前面介绍了享元模式的结构与特点,并且用代码展示了一下享元模式,下面我就来介绍一下它适用的应用场景。享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能,所以以下几种情形适合采用享元模式。

  • 1 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源
  • 2 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态
  • 3 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式

 享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

实际的开发过程中,很多地方不是简单的单存享元就能够实现的,所以享元模式一般和组合模式,工厂模式,单例模式一起开发使用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导言
    • 一、常量池
      • 二、定义
        • 三、实现单纯享元模式
          • 四、实现复合享元模式
            • 四、享元模式的应用
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档