技术成神之路:设计模式(十四)享元模式
介绍
享元模式(Flyweight Pattern)是一种结构性设计模式,旨在通过共享对象来有效地支持大量细粒度的对象。
1.定义
享元模式通过将对象状态分为内部状态(可以共享)和外部状态(不可共享),来减少内存使用和提高性能。
2. 主要作用
- 降低内存消耗
- 提高性能
- 共享相似对象
3. 解决的问题
当程序中存在大量相似对象时,使用享元模式可以有效减少内存占用,避免重复对象的创建。
4. 模式原理
包含角色:
- Flyweight: 抽象享元类,定义了享元对象的接口。
- ConcreteFlyweight: 具体享元类,实现了抽象享元类的接口,负责存储内部状态。
- FlyweightFactory: 享元工厂类,用于创建和管理享元对象,确保共享。
UML类图:
示例:
模拟一个图形绘制的场景,其中使用享元模式共享相同的图形对象(如圆):
// 享元接口
interface Shape {
void draw(String color);
}
// 具体享元类
class Circle implements Shape {
private String color;
public Circle(String color) {
this.color = color;
System.out.println("Creating Circle of color: " + color);
}
@Override
public void draw(String color) {
System.out.println("Drawing Circle of color: " + color);
}
}
// 享元工厂类
class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
}
return circle;
}
}
// 使用
public class FlyweightPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
// 共享相同颜色的圆
Shape circle1 = shapeFactory.getCircle("Red");
circle1.draw("Red");
Shape circle2 = shapeFactory.getCircle("Green");
circle2.draw("Green");
Shape circle3 = shapeFactory.getCircle("Red");
circle3.draw("Red");
System.out.println("Total Circles created: " + ShapeFactory.circleMap.size());
}
}
打印输出:
Creating Circle of color: Red
Drawing Circle of color: Red
Creating Circle of color: Green
Drawing Circle of color: Green
Drawing Circle of color: Red
Total Circles created: 2
🆗,从这个简单的示例,你会发现享元模式其实很简单,就是共享对象 复用对象,一般开发中并不常用。
在安卓中 Handler
的Message
就使用了享元模式,因为在安卓中 几乎所有事件驱动都是通过Message
来进行的,可以说它无处不在,这时候就可以使用享元模式以优化内存使用和提高性能。
下面就以 Message
为例,从源码角度剖析其实现原理!
Message 的池化机制
在 Message 类中,有一个静态的对象池,用于存放可重用的 Message 实例。
public static final Object sPoolSync = new Object();
private static Message sPool;//这是一个链表的头指针,指向池中可重用的 Message 对象
private static int sPoolSize = 0;//记录池中当前的对象数量。
private static final int MAX_POOL_SIZE = 50;//定义池的最大容量。
private static boolean gCheckRecycle = true;//一个标志位,用于检查回收的消息是否有效。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
这里所谓的池 并不是一个集合容器 而是 Message
对象通过 next
属性构成一个链表。每个 Message
对象可以指向下一个可用的 Message
,实现了简单的对象池。
当对象不再需要时(如在处理完消息后),调用recycle()
可以将其放回池中(通过设置 next
指向池头 sPool
),这样下次调用 obtain
时就能复用这些对象,而不是每次都新建。
在调用recycle()
可以发现 有一个 isInUse()
方法判断这个Message
是否正在使用,这个其实不用开发者操心的,因为所有的消息都要经过Looper
这个 “传送带”,内部自动回收,翻开Looper
的源码文件你会发现在loopOnce
方法中最后会调用 msg.recycleUnchecked()
,如果你进行了某种自定义操作,导致 Message
未能通过正常的 Handler
流程处理,那么你可能需要手动调用 msg.recycle()
。
简单概括就是:一条由Message
组成的链表,你想用Message
时,就从这个链表上 掐掉一个Message
来使用,这个掐掉操作就是将m.next = null
,当你不再使用时 就将其放回到链表头,操作就是 next = sPool
和sPool = this
。
5. 优缺点
优点:
- 节省内存空间。
- 提高性能,特别是创建和管理大量对象时。
缺点:
- 外部状态管理可能导致系统逻辑混乱。
6. 应用场景
- 游戏中的角色、场景元素(如树、建筑)等。
- 文本处理系统中的字符、字体。
- 大量相似对象需要频繁创建的场景。
- …
7. 总结
享元模式通过共享对象来优化内存使用和性能,适合于需要创建大量相似对象的场景,但设计复杂性和状态管理需要谨慎处理。