Motivation(动机)
所谓动机就是为什么我们要使用享元模式。我们先来看看一个场景,在一个战争游戏中,我们需要创建很多个士兵对象。每个士兵对象都有一些状态,外貌、行为(移动、开火),在战场上的健康状态和在战场上的位置。
如果我们为每一个士兵都创建一个对象,并每个对象存储各自的状态。可想而知,这对内存的消耗是很大的。为此,我们需要一种方法来减少对象的创建,于是享元模式就应运而生了。
Intent(意图)
运用共享技术有效地支持大量细粒度的对象,这些对象的内部状态是相同的,而其他状态可以是不同的。
Roles(角色)
享元模式类图如下:
- Flyweight: 声明一个可以接收并作用外部状态的接口
- ConcreteFlyweight: 实现Flyweight并存储内部状态。一个ConcreteFlyweight对象必须是可以共享的,它能够维持内部状态并且能够操纵外部状态。在战争游戏中,士兵的外貌和行为是内部状态,士兵在战场上的健康状态和位置是外部状态。士兵移动的行为可以操作其他状态(位置)的改变。
- FlyweightFactory: 创建和管理Flyweight对象的工厂,能够确保Flyweight对象的共享。它维持了一个保存不同Flyweight对象的池。如果客户端请求的Flyweight对象在池中存在,那么就返回该对象。否则,就创建客户端请求的对象,加入池中并返回给客户端。
Example(示例)
这个战争游戏实例化了5个SoldierClient,每个SoldierClient都维持了Soldier的外部状态。但是仅仅只有一个Soldier对象被使用。
Java Code(该示例的Java代码)
Soldier接口代码如下:(Flyweight)
public interface Soldier {
/**
* 将一个士兵对象从一个位置移动到另一个位置。
* 注意,士兵的位置是外部状态
*/
public void moveSoldier(int previousLocationX, int previousLocationY, int newLocationX, int newLocationY);
}
SoldierImp代码如下:(ConcreteFlyweight)
public class SoldierImp implements Soldier {
/**
* 维持士兵外貌这个内部状态
*/
private Object soldierGraphicalRepresentation;
/**
* 这个方法接收士兵位置。
* 士兵位置是外部状态,SoldierImp本身不维持先前位置和当前位置的引用。
*/
public void moveSoldier(int previousLocationX, int previousLocationY, int newLocationX, int newLocationY) {
// 删除士兵旧位置的描绘
// 在新位置渲染出士兵对象
}
}
SoldierFactory代码如下:(FlyweightFactory)
public class SoldierFactory {
/**
* 仅仅只有一个士兵对象的池
* 如果拥有多个士兵类型,可以使用数组、list,最好是HashMap
*/
private static Soldier SOLDIER;
/**
* 获取士兵对象
*/
public static Soldier getSoldier() {
//判断士兵对象是否存在
if(SOLDIER==null) {
//不存在就创建新的对象
SOLDIER = new SoldierImp();
}
//返回士兵对象
return SOLDIER;
}
}
注意:上面的代码是存在线程问题的,下面不涉及多线程问题,所以这里的代码就这样写了
SoldierClient
public class SoldierClient {
private Soldier soldier = SoldierFactory.getSoldier();
//存储士兵当前位置状态
private int currentLocationX = 0;
private int currentLocationY = 0;
public void moveSoldier(int newLocationX, int newLocationY) {
soldier.moveSoldier(currentLocationX, currentLocationY, newLocationX, newLocationY);
currentLocationX = newLocationX;
currentLocationY = newLocationY;
}
}
WarGame
public class WarGame {
public static void main(String[] args) {
//start war
//draw war terrian
//创建5个士兵
SoldierClient warSoldiers[] = {
new SoldierClient(),
new SoldierClient(),
new SoldierClient(),
new SoldierClient(),
new SoldierClient()
};
// 移动士兵
// 获取用户输入移动各个士兵
warSoldiers[0].moveSoldier(17, 2112);
warSoldiers[1].moveSoldier(137, 112);
// 注意到这里只有一个SoldierImp提供给5个士兵。
// SoldierClient只需要维持很少的状态,所以它的大小是很小的
// SoldierImp的大小可能很大,也可能很小
// 然而我们可以节省下创建5个士兵外表的内存消耗
}
}