设计模式(7):享元模式及其在JDK中的典型应用

享元模式及其在JDK中的典型应用示例

1、什么是享元模式

2、享元模式的特性

3、享元模式的优缺点及应用场景

4、享元模式使用示例

5、享元模式在JDK中的典型应用示例


1、什么是享元模式

享元模式定义:运用共享技术有效地支持大量细粒度的对象。享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。它提供了减少对象数量从而改善应用所需对象结构的方式。享元模式属于一定结构型模式。

2、享元模式的特性

(1)意图:

运用共享技术有效地支持大量细粒度的对象。

(2)主要解决:

在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

(3)何时使用:

① 系统中有大量对象;

② 这些对象消耗大量内存;

③ 这些对象的状态大部分可以外部化;

④ 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替; 

⑤ 系统不依赖于这些对象身份,这些对象是不可分辨的。

(4)如何解决:

用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

(5)关键代码:用 HashMap 存储这些对象,也可以用数组存储。

3、享元模式的优缺点及应用场景

(1)优点:

大大减少对象的创建,降低系统的内存,使效率提高。如果系统有大量类似的对象,可以节省大量的内存及CPU资源。

(2)缺点:

提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

(3)使用场景:

1)系统有大量相似对象。

2)需要缓冲池的场景。

(4)注意事项: 

1)注意划分外部状态和内部状态,否则可能会引起线程安全问题。

2)这些类必须有一个工厂对象加以控制。

4、享元模式使用示例

(1)场景假设:

假设我们在开发一款地图,地图上有很多树,树只有几种。在地图上使用树的排列组合绘制出森林。 对此,我们不必每次都new一个树,可以使用享元模式来减少内存消耗。

(2)代码实现:

1)定义一个Tree类:定义一个Tree类并将其中的data变量设置为final不可改变,这样可以保证访问安全性:

//定义一个树的类
class Tree{
    private final String name;
    private final String data;

    public Tree(String name, String data) {
        this.name = name;
        this.data = data;
        System.out.println("name: "+name+" tree created!");
    }

    public String getName() {
        return name;
    }


    public String getData() {
        return data;
    }

    @Override
    public String toString() {
        return "Tree{" +
                "name='" + name + '\'' +
                ", data='" + data + '\'' +
                '}';
    }
}

2)定义一个节点坐标类,表示地图中的树:

//坐标类,用来表示树的位置,定义成节点
class TreeNode{
    private int x;
    private int y;
    private Tree tree;

    public TreeNode(int x, int y, Tree tree) {
        this.x = x;
        this.y = y;
        this.tree = tree;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public Tree getTree() {
        return tree;
    }

    public void setTree(Tree tree) {
        this.tree = tree;
    }
}

3)定义一个工厂类TreeFactory,用于创建Tree:

//定义一个工厂来创建树
//每次创建树时先从Map容器中查找存不存在,若存在,直接获取返回;若不存在,先创建,然后再放入容器中,并返回。
class TreeFactory{
    private static Map<String,Tree> map = new ConcurrentHashMap<>();
    public static Tree getTree(String name,String data){
        if(map.containsKey(name)){
            //存在则从容器中获取返回
            return map.get(name);
        }
        //不存在则创建,并放入容器,再返回
        Tree tree = new Tree(name, data);
        map.put(name,tree);
        return tree;
    }
}

4)使用示例如下:

public class FlyWeightPatternTest {

    public static void main(String[] args) {
        //使用示例:
        TreeNode treeNode1 = new TreeNode(3,4,TreeFactory.getTree("xxx","tree data"));
        TreeNode treeNode2 = new TreeNode(5,4,TreeFactory.getTree("xxx","tree data"));
        TreeNode treeNode3 = new TreeNode(1,4,TreeFactory.getTree("xxx","tree data"));
        TreeNode treeNode4 = new TreeNode(7,4,TreeFactory.getTree("xxx","tree data"));
        //从结果输出,只创建了一次

        TreeNode treeNode5 = new TreeNode(3,4,TreeFactory.getTree("yyy","tree data"));
        TreeNode treeNode6 = new TreeNode(5,4,TreeFactory.getTree("yyy","tree data"));
        TreeNode treeNode7 = new TreeNode(1,4,TreeFactory.getTree("yyy","tree data"));
        TreeNode treeNode8 = new TreeNode(7,4,TreeFactory.getTree("yyy","tree data"));
    }
}

5)结果输出如下:

name: xxx tree created!
name: yyy tree created!

可以看出,这里我们程序需要多次创建相同对象,但只有第一次创建的时候才真正创建,后续需要的相同对象都是来源于同一个对象,通过享元模式,我们实现了对象的共享。这里我们使用的是懒汉模式,只有在第一次需要创建对象时才会创建,当然这里也可以提前创建好所有对象,每次需要使用时直接从Map容器中获取返回即可。

5、享元模式在JDK中的典型应用示例

(1)String类对象,Java中的String对象保存在字符串常量池中;

(2)数据库的数据池;

(3)JDK中的com.sun.org.apache.bcel.internal.generic.InstructionConst类也是享元模式的典型应用。

源码如下:

/**
 * This interface contains shareable instruction objects.
 *
 * In order to save memory you can use some instructions multiply,
 * since they have an immutable state and are directly derived from
 * Instruction.  I.e. they have no instance fields that could be
 * changed. Since some of these instructions like ICONST_0 occur
 * very frequently this can save a lot of time and space. This
 * feature is an adaptation of the FlyWeight design pattern, we
 * just use an array instead of a factory.
 *
 * The Instructions can also accessed directly under their names, so
 * it's possible to write il.append(Instruction.ICONST_0);
 *
 * @version $Id: InstructionConst.java 1695415 2015-08-12 01:02:39Z chas $
 */
public final class InstructionConst {

    /**
     * Predefined instruction objects
     */
    /*
     * NOTE these are not currently immutable, because Instruction
     * has mutable protected fields opcode and length.
     */
    public static final Instruction NOP = new NOP();
    public static final Instruction ACONST_NULL = new ACONST_NULL();
    public static final Instruction ICONST_M1 = new ICONST(-1);
    public static final Instruction ICONST_0 = new ICONST(0);
    public static final Instruction ICONST_1 = new ICONST(1);
    public static final Instruction ICONST_2 = new ICONST(2);
    public static final Instruction ICONST_3 = new ICONST(3);
    public static final Instruction ICONST_4 = new ICONST(4);
    public static final Instruction ICONST_5 = new ICONST(5);
    public static final Instruction LCONST_0 = new LCONST(0);
    public static final Instruction LCONST_1 = new LCONST(1);
    public static final Instruction FCONST_0 = new FCONST(0);
    public static final Instruction FCONST_1 = new FCONST(1);
       ......
    public static final ReturnInstruction DRETURN = new DRETURN();
    public static final ReturnInstruction ARETURN = new ARETURN();
    public static final ReturnInstruction RETURN = new RETURN();
    public static final Instruction ARRAYLENGTH = new ARRAYLENGTH();
    public static final Instruction ATHROW = new ATHROW();
    public static final Instruction MONITORENTER = new MONITORENTER();
    public static final Instruction MONITOREXIT = new MONITOREXIT();

    /** You can use these constants in multiple places safely, if you can guarantee
     * that you will never alter their internal values, e.g. call setIndex().
     */
    public static final LocalVariableInstruction THIS = new ALOAD(0);
    public static final LocalVariableInstruction ALOAD_0 = THIS;
    public static final LocalVariableInstruction ALOAD_1 = new ALOAD(1);
    public static final LocalVariableInstruction ALOAD_2 = new ALOAD(2);
    public static final LocalVariableInstruction ILOAD_0 = new ILOAD(0);
    public static final LocalVariableInstruction ILOAD_1 = new ILOAD(1);
    public static final LocalVariableInstruction ILOAD_2 = new ILOAD(2);
    public static final LocalVariableInstruction ASTORE_0 = new ASTORE(0);
    public static final LocalVariableInstruction ASTORE_1 = new ASTORE(1);
    public static final LocalVariableInstruction ASTORE_2 = new ASTORE(2);
    public static final LocalVariableInstruction ISTORE_0 = new ISTORE(0);
    public static final LocalVariableInstruction ISTORE_1 = new ISTORE(1);
    public static final LocalVariableInstruction ISTORE_2 = new ISTORE(2);

    /** Get object via its opcode, for immutable instructions like
     * branch instructions entries are set to null.
     */
    private static final Instruction[] INSTRUCTIONS = new Instruction[256];

    static {
        INSTRUCTIONS[Const.NOP] = NOP;
        INSTRUCTIONS[Const.ACONST_NULL] = ACONST_NULL;
        INSTRUCTIONS[Const.ICONST_M1] = ICONST_M1;
        ......
        INSTRUCTIONS[Const.FRETURN] = FRETURN;
        INSTRUCTIONS[Const.DRETURN] = DRETURN;
        INSTRUCTIONS[Const.ARETURN] = ARETURN;
        INSTRUCTIONS[Const.RETURN] = RETURN;
        INSTRUCTIONS[Const.ARRAYLENGTH] = ARRAYLENGTH;
        INSTRUCTIONS[Const.ATHROW] = ATHROW;
        INSTRUCTIONS[Const.MONITORENTER] = MONITORENTER;
        INSTRUCTIONS[Const.MONITOREXIT] = MONITOREXIT;
    }

    private InstructionConst() { } // non-instantiable

    /**
     * Gets the Instruction.
     * @param index the index, e.g. {@link Const#RETURN}
     * @return the entry from the private INSTRUCTIONS table
     */
    public static Instruction getInstruction(final int index) {
        return INSTRUCTIONS[index];
    }
}

可以知道,这里使用享元模式,提前将一些指令存放于数组中,从而在指令使用非常频繁的场景下节省了主机大量的时间和空间开销。

 

本文源代码:

https://github.com/JianfuYang/2020-yjf-review/tree/master/src/designpatterns/flyweight

 

声明:本文部分内容整理来源于网络,仅做个人学习使用!侵删~

本文部分内容参考链接:

https://www.runoob.com/design-pattern/flyweight-pattern.html

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值