使用共享对象,可有效支持大量的细粒度对象
结构型
当需要创建大量相似对象时,会导致性能下降,可以考虑将相似的对象进行复用;复用的方法就是:将公共不变的内部状态和变化的外部状态分离,通过享元工厂类来获取对象,如果对象已经存在缓存池中,则直接返回缓存池中的对象,如果不存在则创建并存入缓存池中。
有些文章里使用游戏里英雄的例子,认为英雄这个对象是可以使用享元模式复用的,把英雄的基本属性作为内部状态,英雄坐标、装备、状态等作为外部状态,在使用享元工厂类获取到对象后,修改它的外部状态,就得到了一个新的英雄,这是没问题,但是由于复用的是同一个对象,改变了外部状态,之前创建的英雄的外部状态也会一起改变,这显示是会有问题的
class ChessPieces(private val color: String) {
fun downPieces(x: Int,y:Int) {
Tool.print("${color}棋子,落子位置:${x},${y}")
}
}
class PiecesColor {
companion object{
val WHITE ="红色"
val BLACK ="黑色"
}
}
class PiecesFactory {
val mCachePieces = HashMap<String, ChessPieces>()
fun getChessPieces(color: String): ChessPieces {
var chessPieces = mCachePieces[color]
if (chessPieces == null) {
chessPieces = ChessPieces(color)
mCachePieces[color] = chessPieces
}
return chessPieces
}
}
fun main(args: Array<String>) {
val factory = PiecesFactory()
var chessPieces = factory.getChessPieces(PiecesColor.WHITE)
chessPieces.downPieces(1, 2)
chessPieces = factory.getChessPieces(PiecesColor.BLACK)
chessPieces.downPieces(7, 8)
chessPieces = factory.getChessPieces(PiecesColor.WHITE)
chessPieces.downPieces(3, 4)
chessPieces = factory.getChessPieces(PiecesColor.BLACK)
chessPieces.downPieces(9, 10)
println("缓存的对象:${factory.mCachePieces}")
}
运行结果:
2019-08-28 16:27:24.122:红色棋子,落子位置:1,2
2019-08-28 16:27:24.123:黑色棋子,落子位置:7,8
2019-08-28 16:27:24.123:红色棋子,落子位置:3,4
2019-08-28 16:27:24.123:黑色棋子,落子位置:9,10
缓存的对象:{黑色=com.example.designpattern.flyweight.ChessPieces@5451c3a8, 红色=com.example.designpattern.flyweight.ChessPieces@2626b418}
Process finished with exit code 0
上面例子可以看到,棋子的颜色是属于内部不容易发生变化的状态,而位置而是会变化的,当想要获取某个颜色棋子的时候,先从缓存对象池里找,如果有的话直接返回,没有则创建并加入缓存对象池中,从而复用不同颜色棋子,可以看到虽然获取了很多个棋子,但是最终缓存池中就只有黑色和白色两个颜色的棋子对象
只要是有对象复用的地方就会有享元模式的影子,比如:ListView、GridView、RecycleView对应的Adapter中的getView方法就会复用已有的View,从而避免大量的创建View;
我们在发送消息的时候,通常都是建议通过Message.obtain()来获取一个Message对象,而不是直接new Message(),这是因为Message.obtain方法里用到了享元模式,会复用已经存在的Message对象,先来看看Message的obtain方法:
public final class Message implements Parcelable {
.. ..
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
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();
}
.. ..
}
从代码里可以看到,obtain方法就相当于享元工厂类,先判断对象池里是否有缓存的对象,没有的话就创建一个新的对象,Mesage里的对象池采用的是链表结构实现,sPool总是指向链表的第一个元素,跟标准的享元模式不一样的地方是它创建新的对象之后,并没有直接加入到缓存池当中,通过源码分析可以知道,当在Looper中Message处理完毕之后会调用recycleUnchecked方法,在这个方法里才会回收Message对象,加入到缓冲池里:
Looper.java
public static void loop() {
.. ..
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
.. ..
try {
msg.target.dispatchMessage(msg);
} finally {
.. ..
}
.. ..
msg.recycleUnchecked();//回收Message
}
}
Message.java
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 = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}