享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。
举例:
休闲开发游戏中,比如说围棋、五子棋、跳棋等,都有大量的棋子对象,如果用常规的面向对象方式编程,就会产生大量的对象,这样服务器就很难支持大规模量的玩家玩游戏了,因为内存有限。如果使用享元模式,那么棋子对象可以减少到2个实例,其中不同颜色的棋子就是两个不互相共享的享元类。
- 棋子抽象类,即享元类
abstract class Chess {
abstract void buildChess();
}
- 享元实现类,两个互不干涉颜色的棋子
class BlackChess extends Chess {
@Override
void buildChess() {
System.out.println("创建了一个黑色棋子");
}
}
class WhiteChess extends Chess {
@Override
void buildChess() {
System.out.println("创建了一个白色棋子");
}
}
- 享元工厂类
class ChessFactory {
private static HashMap<String, Chess> chessMap = new HashMap<>();
// 获取各个棋子
static Chess getChess(String color) {
if (!chessMap.containsKey(color)) {
Chess chess = null;
if ("白色".equals(color)) {
chess = new WhiteChess();
} else {
chess = new BlackChess();
}
chessMap.put(color, chess);
return chess;
} else {
return chessMap.get(color);
}
}
static int getChessCount() {
return chessMap.size();
}
}
- 主程序
class Test {
public static void main(String[] args) {
Chess white1 = ChessFactory.getChess("白色");
Chess black1 = ChessFactory.getChess("黑色");
Chess white2 = ChessFactory.getChess("白色");
Chess black2 = ChessFactory.getChess("黑色");
Chess white3 = ChessFactory.getChess("白色");
Chess black3 = ChessFactory.getChess("黑色");
white1.buildChess();
white2.buildChess();
white3.buildChess();
System.out.println(white1 == white2);
System.out.println(white2 == white3);
black1.buildChess();
black2.buildChess();
black3.buildChess();
System.out.println(black1 == black2);
System.out.println(black2 == black3);
System.out.println("棋子个数:" + ChessFactory.getChessCount());
}
}
结果:
创建了一个白色棋子
创建了一个白色棋子
创建了一个白色棋子
true
true
创建了一个黑色棋子
创建了一个黑色棋子
创建了一个黑色棋子
true
true
棋子个数:2
可以看出来,虽然利用工厂创建了6个棋子对象,但实际对象只有2个。
内部状态与外部状态:
在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来标识数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传进来,就可以通过共享大幅度的减少单个实例的数目。
举例:
刚才的例子中,只有棋子的存在,这样实现多局游戏,就需要加入一个棋盘信息,表示是哪一局游戏,继续对代码进行调整:
- 添加棋盘类
class Chessboard {
private String name;
public Chessboard(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
- 调整享元类
abstract class Chess {
abstract void buildChess(Chessboard chessboard);
}
class WhiteChess extends Chess {
@Override
void buildChess(Chessboard chessboard) {
System.out.println("在棋盘:" + chessboard.getName() + " 创建了一个白色棋子");
}
}
class BlackChess extends Chess {
@Override
void buildChess(Chessboard chessboard) {
System.out.println("在棋盘:" + chessboard.getName() + " 创建了一个黑色棋子");
}
}
- 主程序
class Test {
public static void main(String[] args) {
Chess white1 = ChessFactory.getChess("白色");
Chess black1 = ChessFactory.getChess("黑色");
Chess white2 = ChessFactory.getChess("白色");
Chess black2 = ChessFactory.getChess("黑色");
Chess white3 = ChessFactory.getChess("白色");
Chess black3 = ChessFactory.getChess("黑色");
white1.buildChess(new Chessboard("对局一"));
white2.buildChess(new Chessboard("对局二"));
white3.buildChess(new Chessboard("对局三"));
System.out.println(white1 == white2);
System.out.println(white2 == white3);
black1.buildChess(new Chessboard("对局四"));
black2.buildChess(new Chessboard("对局五"));
black3.buildChess(new Chessboard("对局六"));
System.out.println(black1 == black2);
System.out.println(black2 == black3);
System.out.println("棋子个数:" + ChessFactory.getChessCount());
}
}
结果:
在棋盘:对局一 创建了一个白色棋子
在棋盘:对局二 创建了一个白色棋子
在棋盘:对局三 创建了一个白色棋子
true
true
在棋盘:对局四 创建了一个黑色棋子
在棋盘:对局五 创建了一个黑色棋子
在棋盘:对局六 创建了一个黑色棋子
true
true
棋子个数:2
使用场景
如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时就可以考虑使用享元模式。 不过享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要消耗资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供享元时才值得使用享元模式。