Java设计模式之享元模式

定义

运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

结构

享元模式中存在以下两种状态:

  • 内部状态,即不会随着环境的改变而改变的可共享部分。
  • 外部状态,指随环境改变而改变的不可以共享的部分。

享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

享元模式的主要有以下角色:

  • 抽象享元角色:通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

案例

俄罗斯方块中的方块不需要多次创建,使用享元模式可以省下极大内存空间

创建抽象享元角色

java 复制代码
public abstract class AbstractBox {
    //相当于内部状态为形状
    public abstract String getShape();
    //外部状态为颜色
    public void getColor(String color){
        System.out.println("形状为:"+getShape()+",颜色为:"+color);
    }
}

创建具体享元角色

java 复制代码
public class IBox extends AbstractBox{
    @Override
    public String getShape() {
        return "I";
    }
}

public class LBox extends AbstractBox{
    @Override
    public String getShape() {
        return "L";
    }
}

public class OBox extends AbstractBox{
    @Override
    public String getShape() {
        return "O";
    }
}

创建享元工厂

java 复制代码
public class BoxFactory {
    private static BoxFactory boxFactory = new BoxFactory();
    //工厂使用单例模式
    public static BoxFactory getInstance(){
        return boxFactory;
    }

    private Map<String,AbstractBox> map;

    public BoxFactory() {
        map = new HashMap<>();
        map.put("I",new IBox());
        map.put("L",new LBox());
        map.put("O",new OBox());
    }

    public AbstractBox getBox(String key){
        return map.get(key);
    }
}

测试

java 复制代码
public class Client {
    public static void main(String[] args) {
        BoxFactory factory = BoxFactory.getInstance();
        AbstractBox IBox = factory.getBox("I");
        IBox.getColor("绿色");
        AbstractBox LBox = factory.getBox("L");
        LBox.getColor("白色");
        AbstractBox LBox2 = factory.getBox("L");
        LBox2.getColor("黑色");
        System.out.println(LBox==LBox2);
    }
}

形状为:I,颜色为:绿色

形状为:L,颜色为:白色

形状为:L,颜色为:黑色

true

根据运行结果可以看出来外部状态(颜色)的改变不会影响共享。

优缺点

优点

  • 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立,且不影响内部状态

缺点:

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

使用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。(案例中的Map作为容器)

JDK源码解析

Integer类中的ValueOf()方法就使用到了享元模式

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;

        System.out.println(i1 == i2);

        Integer i3 = 128;
        Integer i4 = 128;

        System.out.println(i3 == i4);
    }
}

ture

false

由结果可以看到这里产生了两种不同的结果,对于int类型的引用变量,装箱为Integer类型的对象,使用的是ValueOf()方法,查看ValueOf源码

这里可以看到,对于传过来的参数有一个判断操作,点进去IntegerCache查看对应的low与high属性是什么

java 复制代码
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

这个静态类初始化low为-128以及定义一个high属性以及一个Integer类型的cache数组。

读取官方注释,我们可以猜测出,high属性的值可以在初始化JVM时进行更改。在静态代码块做的事情就是判断high的值是否有设置,如果没有更改就不进行if判断对h的值进行修改,如果修改了high属性值,则要进行判断是否将设置的值对h进行替换作为high属性。

对high进行完赋值后,初始化cache数组,数组长度为最大值与最小值的范围长度。然后通过for循环对范围内的所有值创建成Integer对象存放在数组中

在客户端使用时,先进行判断数组中是否存在了这个数的对象,如果存在直接返回即可,如果不存在则要进行创建Integer对象返回。

相关推荐
fangzhanpeng1684 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心7 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552712 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552713 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝14 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552714 天前
Java图像处理实战指南
人工智能·1024程序员节
u01030552715 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552717 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552718 天前
Java I/O核心操作实战
人工智能·1024程序员节
u01030552719 天前
ArrayList操作详解与实战应用
人工智能·1024程序员节