目录
十二、享元模式
1、概述
享元模式是对对象的复用,以复用之前的对象取代对象的创建,那么这个对象必须是用完即扔的!也就是说用完可以销毁,但是后面来了个人说别扔了我改改还能用!在生活中就像吃饭的碗一样,不可能吃完饭碗就扔了,而是洗干净下次再用!还有就是我们对碗的需求是有时候需要一个大碗有时候需要一个小碗,第一次需要的时候可能没有,就造一个(现实中一般是买),后来就不用造了,直接复用以前的,在程序中对象也不存在耐久度(损耗)这个概念!
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。
例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
这让我瞬间想起了原型模式!事实上这是不一样的!原型模式是克隆,享元模式是复用!
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
2、应用场景
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。
-
系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源;
-
大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态;
-
由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式;
3、优缺点
优点
相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力;
缺点
-
为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性;
-
读取享元模式的外部状态会使得运行时间稍微变长;
4、主要角色
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
内部状态指对象共享出来的信息(固定不变的内容),存储在享元信息内部,并且不会随环境的改变而改变;
外部状态指对象得以依赖的一个标记(变化的内容),随环境的改变而改变,不可共享;
比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
-
抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入;
-
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口;
-
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中;
-
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象;
5、代码实现
下面的例子是一个画圆操作,颜色有5种,x和y轴的直径是变化的,我们只需要创建5种颜色的圆,然后对进行复用就行了!
也许在某些场景下,x和y是固定的,只有颜色不固定,就不需要设置x和y了,甚至连颜色也是固定的,那直接单例模式了!
这个享元模式,我们可以转换成有限多利模式,不就是5种颜色嘛,创建5个实例,根据颜色匹配即可!不过例子中的写法是什么时候用到了这个颜色的圆,什么时候创建,这未免不是一种很好的方案!
当然这是一个简单的例子,甚至连颜色都设置成变量,创建一个圆,对这个圆进行复用,但是需要设置属性值的数量每增加一个就会降低很多程序运行的性能!在复杂的业务环境中应该适当选择享元角色!
抽象享元角色:形状
package com.zibo.design.thirteen;
// 抽象享元角色:形状
public interface Shape {
void draw();
}
具体享元角色:圆
package com.zibo.design.thirteen;
// 具体享元角色:圆
public class Circle implements Shape {
private final String color;
private int x;
private int y;
public Circle(String color) {
this.color = color;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color
+ ", x : " + x + ", y :" + y + "]");
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
}
享元工厂
package com.zibo.design.thirteen;
import java.util.HashMap;
// 享元工厂:形状工厂
public class ShapeFactory {
// 存储对象以复用
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
// 从map获取圆
Circle circle = (Circle) circleMap.get(color);
// 如果没有这个颜色的,就创建新的
if (circle == null) {
// 根据颜色创建不同的对象,然会对他们复用
// 那么被创建的对象可以算是内部,那些被修改的数据就是外部数据
// 内部数据是固定的,或者是相对固定的
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("创建新颜色的圆形对象:" + color);
}
return circle;
}
}
测试类
package com.zibo.design.thirteen;
public class FlyweightPatternDemo {
private static final String[] colors = {"红色", "绿色", "蓝色", "白色", "黑色"};
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Circle circle = (Circle) ShapeFactory.getCircle(getRandomColor());
// x和y是非享元数据
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.draw();
}
}
private static String getRandomColor() {
return colors[(int) (Math.random() * colors.length)];
}
private static int getRandomX() {
return (int) (Math.random() * 100);
}
private static int getRandomY() {
return (int) (Math.random() * 100);
}
}
6、运行结果
这里一共创建了5个Circle对象
创建新颜色的圆形对象:白色
Circle: Draw() [Color : 白色, x : 46, y :17]
创建新颜色的圆形对象:黑色
Circle: Draw() [Color : 黑色, x : 39, y :5]
Circle: Draw() [Color : 黑色, x : 5, y :76]
创建新颜色的圆形对象:红色
Circle: Draw() [Color : 红色, x : 98, y :82]
创建新颜色的圆形对象:绿色
Circle: Draw() [Color : 绿色, x : 58, y :2]
Circle: Draw() [Color : 黑色, x : 30, y :16]
Circle: Draw() [Color : 红色, x : 74, y :14]
Circle: Draw() [Color : 白色, x : 2, y :16]
创建新颜色的圆形对象:蓝色
Circle: Draw() [Color : 蓝色, x : 44, y :90]
Circle: Draw() [Color : 黑色, x : 51, y :59]
Circle: Draw() [Color : 绿色, x : 87, y :55]
Circle: Draw() [Color : 蓝色, x : 36, y :49]
Circle: Draw() [Color : 红色, x : 11, y :42]
Circle: Draw() [Color : 红色, x : 88, y :38]
Circle: Draw() [Color : 黑色, x : 1, y :46]
Circle: Draw() [Color : 白色, x : 87, y :3]
Circle: Draw() [Color : 蓝色, x : 80, y :12]
Circle: Draw() [Color : 黑色, x : 12, y :19]
Circle: Draw() [Color : 绿色, x : 57, y :24]
Circle: Draw() [Color : 黑色, x : 17, y :46]
7、享元模式扩展
在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式:
这大概是完全共享和部分共享!
-
单纯享元模式,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类;
-
复合享元模式,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享;