享元模式—结构型

简介

享元模式(英文名称:Flyweight Design Pattern),所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。定义中的“不可变对象”指的是,一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或者属性)就不会再被修改了。所以,不可变对象不能暴露任何 set() 等修改内部状态的方法。

使用场景

1,项目中存在大量的相似对象;

2,需要缓冲池的场景

UML类图

享元模式—结构型_第1张图片

FlyweightFactory:享元对象抽象基类或者接口;

ConcreateFlyweight:具体享元对象;

FlyweightFactory:享元工厂,祖泽管理享元对象池和创建享元对象;

示例

文本编辑器可以理解为word,为了简化,假设这个文本编辑器只实现了文字编辑功能,不包含图片、表格等复杂的编辑功能。对于简化之后的文本编辑器,我们要在内存中表示一个文本文件,只需要记录文字和格式两部分信息就可以了,其中,格式又包括文字的字体、大小、颜色等信息。

public class Character {//文字
    private char c;

    private Font font;
    private int size;
    private int colorRGB;

    public Character(char c, Font font, int size, int colorRGB) {
        this.c = c;
        this.font = font;
        this.size = size;
        this.colorRGB = colorRGB;
    }
}
public class Editor {
    private List chars = new ArrayList<>();

    public void appendCharacter(char c, Font font, int size, int colorRGB) {
        Character character = new Character(c, font, size, colorRGB);
        chars.add(character);
    }
}

在文本编辑器中,我们每敲一个文字,都会调用 Editor 类中的 appendCharacter() 方法,创建一个新的 Character 对象,保存到 chars 集合中;如果一个文本文件中,有几十万的文字,那我们就要在内存中存储这么多 Character 对象。这么多的对象很容易导致内存溢出;可以使用享元模式解决以上问题;

通过享元模式改造以上代码

在一个文本文件中,用到的字体格式不会太多,毕竟不大可能有人把每个文字都设置成不同的格式。所以,对于字体格式,我们可以将它设计成享元,让不同的文字共享使用。

享元类:

public class CharacterStyle {
    private Font font;
    private int size;
    private int colorRGB;

    public CharacterStyle(Font font, int size, int colorRGB) {
        this.font = font;
        this.size = size;
        this.colorRGB = colorRGB;
    }
}

享元工厂类 

public class CharacterStyleFactory {
    private static final Map styles = new HashMap<>();

    public static CharacterStyle getStyle(Font font, int size, int colorRGB) {
        String key = font.getName() + size + colorRGB;
        if (styles.containsKey(key)) {
            return styles.get(key);
        }else {
            CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB);
            styles.put(key, newStyle);
            return newStyle;
        }
    }
}
public class Character {
        private char c;
        private CharacterStyle style;

        public Character(char c, CharacterStyle style) {
            this.c = c;
        }
}
public class Editor {
    private List chars = new ArrayList<>();

    public void appendCharacter(char c, Font font, int size, int colorRGB) {
        Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB));
        chars.add(character);
    }
}

Flyweight:享元抽象基类或者接口,如果需求复杂,结构单一可以不用抽取成基类或接口,所以这个可以没有;


在JavaAPI中的运用

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

运行结果: 

true

false

出现以上问题原因: Integer 用到了享元模式来复用对象,才导致了这样的运行结果。当我们通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。看代码更加清晰一些,Integer 类的 valueOf() 函数的具体代码如下所示: 

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
}

这里的IntegerCache就是享元模式的工厂类;只不过名字不叫 xxxFactory 而已;看一下源代码更容易理解;

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() {}
    }

在String中的运用

String s1 = "小哥哥";
String s2 = "小哥哥";
String s3 = new String("小哥哥");
System.out.println(s1 == s2);
System.out.println(s1 == s3);

上面代码的运行结果是:一个 true,一个 false。跟 Integer 类的设计思路相似,String 类利用享元模式来复用相同的字符串常量(也就是代码中的“小哥哥”)。JVM 会专门开辟一块存储区来存储字符串常量,这块存储区叫作“字符串常量池”。

你可能感兴趣的:(设计模式)