参考
C++设计模式——享元模式
享元模式 游戏
设计优化】- 正确使用享元模式
在玩五子棋或象棋的时候,我就想过,腾讯那帮伙计是怎么做的呢?五子棋的棋子有黑白两色,难道每次放一个棋子就new一个对象么?象棋有车、马、相、士、帅、炮和兵,是不是每盘棋都要把所有的棋子都new出来呢?如果真的是每一个棋子都new一个,那么再加上那么多人玩;那要new多少对象啊,如果是这样做的话,我想有多少服务器都是搞不定的,可能QQ游戏大厅会比12306还糟糕。那腾讯那帮伙计是如何实现的呢?那就要说到今天总结的享元模式了。
在GOF的《设计模式:可复用面向对象软件的基础》一书中对享元模式是这样说的:运用共享技术有效地支持大量细粒度的对象。
就如上面说的棋子,如果每个棋子都new一个对象,就会存在大量细粒度的棋子对象,这对服务器的内存空间是一种考验,也是一种浪费。我们都知道,比如我在2013号房间和别人下五子棋,2014号房间也有人在下五子棋,并不会因为我在2013号房间,而别人在2014号房间,而导致我们的棋子是不一样的。这就是说,2013号房间和2014号房间的棋子都是一样的,所有的五子棋房间的棋子都是一样的。唯一的不同是每个棋子在不同的房间的不同棋盘的不同位置上。所以,对于棋子来说,我们不用放一个棋子就new一个棋子对象,只需要在需要的时候,去请求获得对应的棋子对象,如果没有,就new一个棋子对象;如果有了,就直接返回棋子对象。这里以五子棋为例子,进行分析,当玩家在棋盘上放入第一个白色棋子时,此时由于没有白色棋子,所以就new一个白色棋子;当另一个玩家放入第一个黑色棋子时,此时由于没有黑色棋子,所以就需要new一个黑色棋子;当玩家再次放入一个白色棋子时,就去查询是否有已经存在的白色棋子对象,由于第一次已经new了一个白色棋子对象,所以,现在不会再次new一个白色棋子对象,而是返回以前new的白色棋子对象;对于黑色棋子,亦是同理;获得了棋子对象,我们只需要设置棋子的不同棋盘位置即可。
另一个典型应用是在SAAS系统中。SAAS即Software As A Service,是目前比较流行的一种软件应用模式。以一个人事管理系统的SAAS软件为例,假设公司甲、乙、丙均为这个SAAS系统的用户,则定义每个公司为这套系统的一个租户。每个公司(租户)又各有100个员工。如果这些公司的所有员工都可以登录这套系统查看自己的收入情况,并且为了系统安全,每个公司(租户)都拥有自己独立的数据库。为了使系统的设计最为合理,在这种情况下,便可以使用享元模式为每个租户分别提供工资查询的接口,而一个公司(租户)下的所有员工可以共享一个查询(因为一个租户下所有的员工数据都存放在一个数据库中,它们共享数据库连接)。这样,系统只需要3个享元实例,就足以应付300个员工的查询请求了。系统的结构图如下:
通过这个实例,还可以进一步了解享元工厂和对象池的重要区别。在一个对象池中,所有的对象都是等价的,任意两个对象在任何使用场景中都可以被对象池中的其他对象代替。而在享元模式中,享元工厂所维护的素有对象都是不同的,任何两个对象间不能相互代替。
//抽象享元:
public interface IReportManager {
public String createReport();
}
//具体享元类:
public class EmployeeReportManager implements IReportManager{
@Override
public String createReport() {
return "This is a EmployeeReportManager";
}
}
public class FinancialReportManager implements IReportManager{
@Override
public String createReport() {
return "This is a financial report";
}
}
//享元工厂:
public class ReportManagerFactory {
Map<String , IReportManager> financialReportManager =
new HashMap<String , IReportManager>();
Map<String , IReportManager> employeeReportManager =
new HashMap<String , IReportManager>();
public IReportManager getFinancialReportManager(String id){
IReportManager r = financialReportManager.get(id);
if(r==null){
r= new FinancialReportManager();
financialReportManager.put(id, r);
}
return r;
}
public IReportManager getEmployeelReportManager(String id){
IReportManager r = employeeReportManager.get(id);
if(r==null){
r= new EmployeeReportManager();
employeeReportManager.put(id, r);
}
return r;
}
}
//Main方法
public static void main(String[] args){
ReportManagerFactory rmf = new ReportManagerFactory();
IReportManager rm = rmf.getFinancialReportManager("A");
System.out.println(rm.createReport());
}