设计模式之享元模式

229 阅读4分钟

设计模式之享元模式

介绍

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少内存使用和提高性能。它主要适用于当大量相似的对象需要被创建时,通过共享这些相似对象的共同部分,减少重复创建对象的开销。

在享元模式中,将对象分为两种类型:

  1. 内部状态(Intrinsic State):是对象的固定、不可变的部分,在创建对象时确定并共享。这些信息在对象的整个生命周期中都不会改变。

  2. 外部状态(Extrinsic State):是对象的变化、可变的部分,在使用对象时传递给对象,并且可能在运行时改变。外部状态并不影响对象的共享,因此可以在不同的上下文中共享对象。

享元模式的主要目标是最大程度地减少内存使用,通过共享内部状态,将相似对象的数据存储在共享池中,而外部状态可以在运行时传递,使得相同或相似的对象可以被多次重复使用,从而节省了大量的内存和资源。

下面是享元模式的一般实现方式:

  1. 创建享元接口(Flyweight):定义了享元对象需要实现的方法。

  2. 创建具体享元类(ConcreteFlyweight):实现了享元接口,并包含了内部状态。具体享元对象必须是可共享的,即内部状态在创建后不应该被修改。

  3. 创建享元工厂类(FlyweightFactory):负责管理和创建享元对象。它维护一个享元池(Flyweight Pool),用于存储已经创建的享元对象。在需要时,工厂类会根据客户端请求返回已有的享元对象,或者创建新的享元对象。

  4. 客户端代码:通过享元工厂来获取享元对象,并传递外部状态给享元对象。

使用享元模式的关键是要识别出内部状态和外部状态,并将内部状态进行共享,而外部状态通过传递来实现个性化。这样可以极大地减少对象的数量,提高系统性能,并节省内存空间。

享元模式在许多地方有应用,比如线程池、字符串池等。它适用于需要大量相似对象的场景,帮助提高程序的效率和性能。

Coding

创建享元接口

public interface Shape {
    void draw();
}

创建具体享元类

public class Circle implements Shape {
    private String color; //外部状态,由外部去传递

    //长度
    private Integer radius; //内部状态,不可变,

    public Circle(String color) {
        this.color = color;
        this.radius = 5;
    }

    @Override
    public void draw() {
        System.out.println("绘制一个半径为 " + radius + "、颜色为 " + color + " 的圆");
    }
}

创建享元工厂类

public class ShapeFactory {
    private static final Map<String, Shape> circleMap = new HashMap<>();

    public static Shape getCircle(String color) {
        Shape circle  = circleMap.get(color);
        if (circle  == null) {
            circle  = new Circle(color);
            circleMap.put(color, circle );
            System.out.println("创建一个颜色为 " + color + " 的圆对象");
        }
        return circle ;
    }

}

客户端代码

public class Client {
    public static void main(String[] args) {
        // 获取红色圆对象
        Shape redCircle = ShapeFactory.getCircle("红色");
        redCircle.draw();

        // 获取蓝色圆对象
        Shape blueCircle = ShapeFactory.getCircle("蓝色");
        blueCircle.draw();

        // 再次获取红色圆对象,应该直接从缓存中获取,不会创建新对象
        Shape anotherRedCircle = ShapeFactory.getCircle("红色");
        anotherRedCircle.draw();
    }
}


结果

创建一个颜色为 红色 的圆对象
   绘制一个半径为 5、颜色为 红色 的圆
创建一个颜色为 蓝色 的圆对象
   绘制一个半径为 5、颜色为 蓝色 的圆
   绘制一个半径为 5、颜色为 红色 的圆

可以看到第3次没有创建红色的圆,注意到在第一个步骤中已经创建了一个红色圆对象,并将其放入了 circleMap 中。所以第二次获取红色圆对象时,直接从缓存中获取,而不是再次创建新的对象。

源码场景

Integer类在Java中运用了享元模式的概念,它通过缓存常用的整数对象来节省内存和提高性能。

在Java中,整数范围内的数值(通常在-128到127之间)被缓存为Integer对象,以便重复使用相同的对象。这是通过Integer类的内部静态类IntegerCache实现的。

下面是Integer类的相关源码片段:

public final class Integer extends Number implements Comparable<Integer> {
    // ...

    private static classIntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // 配置缓存的上界,可以通过JVM参数来调整
            int h = 127;
            //需要注意的是,具体的缓存范围和缓存大小可以通过JVM参数进行调整。通过增大缓存范围,可以提高整数值的重用性,但会占用更多的内存空间。
            String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // 将配置的上界应用到缓存上
                    h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
                } catch (NumberFormatException nfe) {
                    // 忽略错误的配置值
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for (int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }
    }
//这种设计使得在整数范围内频繁使用的整数值可以重复使用同一个`Integer`对象,从而节省了内存开销。同时,通过缓存机制,可以提高整数对象的获取速度,提高了性能。
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    // ...
}

Integer类中,有一个私有的静态内部类IntegerCache,它定义了一个缓存数组cache,用于存储整数对象。

在静态代码块中,IntegerCache会根据配置的上界和默认范围(-128到127)来初始化缓存数组。对于在缓存范围内的整数值,valueOf()方法会直接返回缓存中的对象,而不是每次都创建新的对象。对于超出缓存范围的整数值,valueOf()方法会创建一个新的Integer对象。

总结起来,Integer类通过缓存常用的整数对象实现了享元模式的概念,从而在整数范

总结

在享元模式中,通过共享相似对象来减少对象的创建。当需要创建对象时,首先检查缓存中是否存在相应的对象。如果存在,直接从缓存中获取;如果不存在,才会创建新的对象并放入缓存中。这样可以节省内存空间,避免创建大量相同属性的对象,提高系统的性能和效率。