前言
主要内容有:
- 该模式的介绍,包括:
- 引子、意图(大白话解释)
- 类图、时序图(理论规范)
- 该模式的代码示例:熟悉该模式的代码长什么样子
- 该模式的优缺点:模式不是万金油,不可以滥用模式
- 该模式的应用案例:了解它在哪些重要的源码中被使用
结构型——享元模式 Flyweight Pattern
引子
主要用于减少创建对象的数量,以减少内存占用和提高性能。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
最经典的享元模式代码:
class FlyweightFactory { //定义一个HashMap用于存储享元对象,实现享元池 private HashMap flyweights = newHashMap(); public Flyweight getFlyweight(String key){ //如果对象存在,则直接从享元池获取 if(flyweights.containsKey(key)){ return(Flyweight)flyweights.get(key); } //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回 else { Flyweight fw = newConcreteFlyweight(); flyweights.put(key,fw); return fw; } } } 复制代码
定义
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
两个概念:
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。
类图
如果看不懂UML类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:
享元模式包含如下角色:
- Flyweight: 抽象享元类
- ConcreteFlyweight: 具体享元类
- UnsharedConcreteFlyweight: 非共享具体享元类
- FlyweightFactory: 享元工厂类
时序图
时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。
我们可以大致浏览下时序图,如果感兴趣的小伙伴可以去深究一下:
代码实现
假设:我们有一个绘图的应用程序,通过它我们可以出绘制各种各样的形状、颜色的图形,那么这里形状和颜色就是内部状态了,通过享元模式我们就可以实现该属性的共享了。
抽象享元类Flyweight:绘制图像的抽象方法
public abstract class Shape { public abstract void draw(); } 复制代码
具体享元类ConcreteFlyweight:例子中则是一种绘制某种图像(圆形)的具体实现类,里面的颜色则是一个可以共享的内部对象。
public class Circle extends Shape{ private String color; public Circle(String color){ this.color = color; } public void draw() { System.out.println("画了一个" + color +"的圆形"); } } 复制代码
享元工厂类FlyweightFactory:
利用了HashMap保存已经创建的颜色
public class FlyweightFactory{ static Map<String, Shape> shapes = new HashMap<String, Shape>(); public static Shape getShape(String key){ Shape shape = shapes.get(key); //如果shape==null,表示不存在,则新建,并且保持到共享池中 if(shape == null){ shape = new Circle(key); shapes.put(key, shape); } return shape; } public static int getSum(){ return shapes.size(); } } 复制代码
客户端调用:
调用相同颜色时,会直接从HashMap中取那个颜色的对象,而不会重复创建相同颜色的对象。
public class Client { public static void main(String[] args) { Shape shape1 = FlyweightFactory.getShape("红色"); shape1.draw(); Shape shape2 = FlyweightFactory.getShape("灰色"); shape2.draw(); Shape shape3 = FlyweightFactory.getShape("绿色"); shape3.draw(); Shape shape4 = FlyweightFactory.getShape("红色"); shape4.draw(); Shape shape5 = FlyweightFactory.getShape("灰色"); shape5.draw(); Shape shape6 = FlyweightFactory.getShape("灰色"); shape6.draw(); System.out.println("一共绘制了"+FlyweightFactory.getSum()+"中颜色的圆形"); } } 复制代码
使用场景举例
如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
Integer 中的享元模式
public static void main(String[] args) { Integer i1 = 12 ; Integer i2 = 12 ; System.out.println(i1 == i2); Integer b1 = 128 ; Integer b2 = 128 ; System.out.println(b1 == b2); } 复制代码
输出是
true false 复制代码
在Java中,Integer是有缓存池的,缓存了-128~127的int对象
IntegerCache 缓存类:
//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。 private static class IntegerCache { static final int low = -128;//区间的最低值 static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数 static final Integer cache[]; //缓存数组 static { // high value may be configured by property int h = 127; //这里可以在运行时设置虚拟机参数来确定h :-Djava.lang.Integer.IntegerCache.high=250 String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) {//用户设置了 int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127);//虽然设置了但是还是不能小于127 // 也不能超过最大值 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } high = h; cache = new Integer[(high - low) + 1]; int j = low; //循环将区间的数赋值给cache[]数组 for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} } 复制代码
其他
同理,Long也有缓存池。
String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。
优缺点
优点
- 享元模式的优点在于它能够极大的减少系统中对象的个数。
- 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
- 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。