前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式——享元模式

设计模式——享元模式

作者头像
Java架构师必看
发布2021-05-14 10:12:17
2850
发布2021-05-14 10:12:17
举报
文章被收录于专栏:Java架构师必看

设计模式——享元模式

强烈推介IDEA2020.2破解激活,IntelliJ IDEA 注册码,2020.2 IDEA 激活码

享元模式(Flyweight Pattern):主要用于减少创建对象的数量,以减少内存占用和提高性能。在面向对象程序的设计过程中,有时需要创建大量相似的对象实例。如果都创建将会消耗很多系统资源,它是系统性能提高的一个瓶颈。但如果将这些对象的相似部分抽取出来共享,则能节约大量的系统资源,这就是享元模式的产生背景。在 Java 中 String 值的存储就使用了享元模式,相同的值只存一个。

 一、基本介绍


1、享元模式(Flyweight Pattern)也叫 “蝇量模式”:运用共享技术有效地支持大量细粒度对象。 2、常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿过来用,避免重新创建,如果没有我们需要的,则创建一个。 3、享元模式能够解决重复对象的内存消耗问题,当系统中有大量相似对象,需要缓冲池时。不需要总创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。 4、享元模式经典的应用的场景就是池技术,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

二、享元模式的特点


享元模式的主要优点:相同对象只要保存一份,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。 享元模式的主要缺点:为了使对象共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。需要分离出外部状态和内部状态,而且外部状态具有固有化性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。 享元模式的主要意图:运行享元模式有效地支持大量细粒度对象。 享元模式主要解决的问题:在有大量相似对象时,有可能会造成内存溢出,我们把其中共同的部分抽取出来,如果有相同的业务请求,直接返回内存中已有的对象,避免重新创建。 享元模式如何解决问题:用唯一标识码判断,如果内存中有,则返回唯一标识所标识的对象。 享元模式关键代码:用 HashMap 存储对象,key 表示唯一标识,value 为共享对象。 享元模式使用场景:1)、系统有大量相似对象。2)、需要缓冲池的场景。 享元模式注意事项:1)、注意划分外部状态和内部状态,否则可能会引起线程安全问题。2)、这些类必须有一个工厂对象加以控制。

三、内部状态和外部状态


1)、享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态,既将对象的信息分为两部分:内部状态和外部状态。 2)、内部状态:指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。 3)、外部状态:指对象得以依赖的一个标记,是随环境改变而改变的,不可共享的状态。

四、享元模式结构类图


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

五、享元模式案例分析


享元模式在五子棋中的应用:包含多个内部状态 “黑” 和 “白” 颜色的棋子和外部状态 棋子的坐标 ,所以适合享元模式、

【1】抽象享元角色:棋子(ChessPieces)类包含了一个落子的方法:downPieces(Point pt)

代码语言:javascript
复制
public interface ChessPieces {
    //落子方法 color:内部状态  pt:外部状态
    public void downPieces(Point pt);
}

【2】具体享元角色:抽象享元角色的实现类(黑子/白子 的实现类)

  ☛  黑子 实现类如下:

代码语言:javascript
复制
public class BlackPieces implements ChessPieces{
    @Override
    public void downPieces(Point pt) {
        System.out.println("当前获取到的为===黑===颜色的棋子");
        System.out.println("坐标X="+pt.getX()+";Y="+pt.getY());
    }
}

  ☞  白子 实现类如下:

代码语言:javascript
复制
public class WhitePieces implements ChessPieces{
    @Override
    public void downPieces(Point pt) {
        System.out.println("当前获取到的为===白===颜色的棋子");
        System.out.println("坐标X="+pt.getX()+";Y="+pt.getY());
    }
}

【3】享元工厂角色:通过内部状态,将对象进行分类存储,相同的内部状态只存一个对象即可。

代码语言:javascript
复制
public class PiecesFactory {
    //存储已创建的棋子   享元模式的精华
    HashMap<String, ChessPieces> pieces = new HashMap<>();
    private final String WRITE = "Write";
    private final String BLACK = "Black";
    //创建一个静态方法 获取棋子对象
    public ChessPieces getPieceInstance(String color) {
        if(pieces.get(color) == null) {
            if(color == WRITE) {
                WhitePieces whitePieces = new WhitePieces();
                pieces.put(color, whitePieces);
            }else if(color == BLACK){
                BlackPieces blackPieces = new BlackPieces();
                pieces.put(color, blackPieces);
            }else {
                System.out.println("不存在的颜色");
                return null;
            }
        }
        return pieces.get(color);
    }

    //查看 hashmap 中总计的实例数量
    public int getInstallCount() {
        return pieces.size();
    }
}

【4】客户端应用:将内部状态传递给工厂类,外部状态传递给具体实现类。

代码语言:javascript
复制
public class Clinet {
    private final static String WRITE = "Write";
    private final static String BLACK = "Black";

    public static void main(String[] args) {
        //创建工程
        PiecesFactory factory = new PiecesFactory();
        //获取白色棋子
        //下琪1 = 白
        ChessPieces piece1 = factory.getPieceInstance(WRITE);
        piece1.downPieces(new Point(1,2));
        //下琪1 = 黑
        ChessPieces pieceB1 = factory.getPieceInstance(BLACK);
        pieceB1.downPieces(new Point(2,2));
        //下琪2 = 白
        ChessPieces piece2 = factory.getPieceInstance(WRITE);
        piece2.downPieces(new Point(2, 3));
        //下琪2 = 黑
        ChessPieces pieceB2 = factory.getPieceInstance(BLACK);
        pieceB2.downPieces(new Point(3,2));
        //下琪3 = 白
        ChessPieces piece3 = factory.getPieceInstance(WRITE);
        piece3.downPieces(new Point(5, 7));
        //下琪3 = 黑
        ChessPieces pieceB3 = factory.getPieceInstance(BLACK);
        pieceB3.downPieces(new Point(6,6));

        /**
         * 结果:
         * 当前获取到的为===白===颜色的棋子
         * 坐标X=1;Y=2
         * 当前获取到的为===黑===颜色的棋子
         * 坐标X=2;Y=2
         * 当前获取到的为===白===颜色的棋子
         * 坐标X=2;Y=3
         * 当前获取到的为===黑===颜色的棋子
         * 坐标X=3;Y=2
         * 当前获取到的为===白===颜色的棋子
         * 坐标X=5;Y=7
         * 当前获取到的为===黑===颜色的棋子
         * 坐标X=6;Y=6
         */

        //重点是,这6颗棋子 总共创建了多少个对象
        System.out.println(factory.getInstallCount());
        /**
         * 输入结果:2   复合享元模式的应用
         */
    }
}

六、享元模式 JDK-Interger 应用源码分析


【1】我们在创建 Interger 对象时,有两种方式:分别是 valueOf() 和 new 的形式,如下:我们会发现 valueOf() 创建的实例是相等的,说明使用了享元模式,下面我们就查看下其源码:

代码语言:javascript
复制
public static void main(String[] args) {
        Integer x = Integer.valueOf(127); // 得到 x实例,类型 Integer
        Integer y = new Integer(127); // 得到 y 实例,类型 Integer
        Integer z = Integer.valueOf(127);//..
        Integer w = new Integer(127);

        //我们会发现valueOf创建的实例是相等的,说明使用了享元模式。new 每次给创建一个新的对象
        System.out.println(x == z ); // true
        System.out.println(w == y ); // false
    }

【2】进入 valueOf 方法:根据源码分析:只有当 -128 <= i >= 127 时,就使用享元模式,从缓存中获取值

代码语言:javascript
复制
public static Integer valueOf(int i) {
    /**
     * IntegerCache.low = -128
     * IntegerCache.highhigh = 127
     * 根据源码分析:只有当 -128 <= i >= 127 时,就使用享元模式,从缓存中获取值
     * IntegerCache 相当于工厂类
     */
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

【3】我们进入工厂角色:Interger 则为我们的具体享元角色。

代码语言:javascript
复制
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    //工厂类中存储对象实例的数组
    static final Integer cache[];

    static {
        ······
        high = 127;
        //定义数组长度 = 127+128+1
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            //循环创建对象,并放入数组缓存
            cache[k] = new Integer(j++);

        // 断言 如果为true 继续执行,false 则抛错
        assert IntegerCache.high >= 127;
    }
}

七、享元模式的注意事项和细节


1)、对享元模式的理解: “享”  表示共享 “元” 表示对象。 2)、系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。 3)、用唯一标识码判断,如果内存中有则直接返回,一般使用 HashMap、HashTable 或者数组之内进行存储。 4)、享元模式提高了系统的复杂度。需要分离内部状态和外部状态。而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是使用享元模式需要注意的地方。 5)、使用享元模式时,注意划分内部状态和外部状态,并且需要一个工厂类对享元角色进行管理。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  •  一、基本介绍
  • 二、享元模式的特点
  • 三、内部状态和外部状态
  • 四、享元模式结构类图
  • 五、享元模式案例分析
  • 六、享元模式 JDK-Interger 应用源码分析
  • 七、享元模式的注意事项和细节
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档