享元模式(Flyweight Pattern)就是搞一个缓存池,找对象先在池中找,如果未找到匹配的对象,再创建新对象。通过重用现有的同类对象减少创建对象的数量,以减少内存占用和提高性能。也是属于结构型模式。
专业点说,享元模式就是运用共享技术有效地支持大量细粒度的对象。
在Java的JDK中,提供了一个字符串缓存池,我们在new一个字符串对象的时候,程序会自动去池中查找,如果有就返回,没有就再去new,然后放入缓存池中。还有线程池,也是运用享元模式的典型案例。
代码实现一下:
参考了知乎一位老师的代码来说明,五子棋游戏的黑白子下棋操作,我觉得很形象:https://zhuanlan.zhihu.com/p/369889407
一、五子棋下棋操作代码:
1.新建一个接口,用来进行对象的共有操作
packagecom.xing.design.flyweight.chess; /*** 共享对象通用的接口 黑棋和白棋都通过这个方法画到棋盘上* @author xing*/publicinterfaceChess { //下棋操作voiddraw(intx,inty); }
2.分别实现不同的子类(黑棋和白棋)
packagecom.xing.design.flyweight.chess; /*** 黑棋* @author xing*/publicclassBlackChessimplementsChess { // 共性 圆形privatefinalStringsharp="圆形"; publicBlackChess() { System.out.println("new一个新黑棋对象"); } publicvoiddraw(intx, inty) { System.out.println("黑色"+sharp+"棋子置于"+x+","+y+"处"); } } packagecom.xing.design.flyweight.chess; publicclassWhiteChessimplementsChess { // 内部状态 圆形privatefinalStringsharp="圆形"; publicWhiteChess() { System.out.println("new一个新白棋对象"); } publicvoiddraw(intx, inty) { System.out.println("白色"+sharp+"棋子置于"+x+","+y+"处"); } }
注意这里的圆形,这个是每个对象都有的内部状态。
3.上面对象有了,再建立一个工厂来生成对象
packagecom.xing.design.flyweight.chess; importjava.util.HashMap; importjava.util.Map; /*** 共享对象工厂 负责提供共享对象,客户端不应该直接实例化棋子对象,而应该使用此工厂来获取。因为我们分了黑白两类对象,所以这里使用Color为key的map来存储共享对象。* @author xing*/publicclassChessFactory { /*** 缓存池*/privatestaticfinalMap<String, Chess>chessMap=newHashMap<>(); /*** 根据唯一的外部状态获取对象* @param color 外部状态 黑色/白色* @return*/publicstaticChessgetChess(Stringcolor) { System.out.println("此时对象缓存池中对象->"+chessMap); Chesschess=chessMap.get(color); if (chess==null) { chess=color.equals("白色") ?newWhiteChess() : newBlackChess(); chessMap.put(color, chess); }else { System.out.println("已经有"+color+"棋子对象啦,不new了,直接用缓存池中的"); } returnchess; } }
这就是核心代码了,我们必须通过这个工厂来获取的对象。
4.客户端测试一下
packagecom.xing.design.flyweight.chess; importjava.util.ArrayList; importjava.util.List; /*** 客户端* @author xing*/publicclassFlyweightClient { publicstaticvoidmain(String[] args) { // 棋盘List<Chess>chessList=newArrayList<>(); //下黑子ChessbackChess1=ChessFactory.getChess("黑色"); backChess1.draw(2, 5); chessList.add(backChess1); //下白子ChesswhiteChess=ChessFactory.getChess("白色"); whiteChess.draw(3, 5); chessList.add(whiteChess); //下黑子ChessbackChess2=ChessFactory.getChess("黑色"); backChess2.draw(2, 6); chessList.add(backChess2); //下白子ChesswhiteChess2=ChessFactory.getChess("白色"); backChess2.draw(3, 8); chessList.add(whiteChess2); System.out.println(String.format("backChess1:%d | backChess2:%d | whiteChess:%d | whiteChess2:%d", backChess1.hashCode(), backChess2.hashCode(), whiteChess.hashCode(), whiteChess2.hashCode())); System.out.println("生成的持久化对象个数->"+chessList.size()); } }
结果:
通过对比两个对象的hashCode,我们发现确实是同一个对象,下了四个棋子,只new了两个对象哈!
再来一个应用场景:
二、缓存一个连接池
换一个应用场景,我们要用享元模式来搞一个缓存池,我们要怎么来实现呢?
1.连接对象抽象一个接口,方便以后增加连接类型
packagecom.xing.design.flyweight.pool; /*** 线程对象公共操作* @author xing* @createTime*/publicinterfaceConnect { /*** 连接*/publicvoidconnect(); }
2.两个实现:
mysqlpackagecom.xing.design.flyweight.pool; /*** Mysql连接对象* @author xing*/publicclassConnectMysqlimplementsConnect{ /**连接地址 */privateStringurl; publicStringgetUrl() { returnurl; } publicvoidsetUrl(Stringurl) { this.url=url; } publicConnectMysql() { System.out.println("new了一个Mysql连接对象"); } publicvoidconnect() { System.out.println("Mysql连接成功,连接信息:【"+url+"】"); } } oraclepackagecom.xing.design.flyweight.pool; /*** Oracle连接对象* @author xing*/publicclassConnectOracleimplementsConnect{ /**连接地址 */privateStringurl; /**用户名 */privateStringuserName; /**密码 */privateStringpassWord; publicStringgetUrl() { returnurl; } publicvoidsetUrl(Stringurl) { this.url=url; } publicStringgetUserName() { returnuserName; } publicvoidsetUserName(StringuserName) { this.userName=userName; } publicStringgetPassWord() { returnpassWord; } publicvoidsetPassWord(StringpassWord) { this.passWord=passWord; } publicConnectOracle() { System.out.println("new了一个Oracle连接对象"); } publicvoidconnect() { System.out.println("Oracle连接成功,连接信息:【地址:"+url+"|用户名:"+userName+"|密码:"+passWord+"】"); } }
3.搞个工厂,核心代码
packagecom.xing.design.flyweight.pool; importjava.util.HashMap; importjava.util.Map; /*** 工厂生成连接对象* @author xing*/publicclassConnectFacatory { /*** 连接池*/privatestaticfinalMap<String, Connect>chessMap=newHashMap<>(); publicstaticConnectgetConnect(Stringtype) { Connectconnect=chessMap.get(type); if(connect!=null) { System.out.println("池中已有"+type+"对象,直接使用"); returnconnect; } switch (type) { case"mysql": connect=newConnectMysql(); chessMap.put(type, connect); break; case"oracle": connect=newConnectOracle(); chessMap.put(type, connect); break; default: System.out.println("暂只支持mysql、oracle连接"+type+"是不可用的连接类型"); connect=null; } returnconnect; } } 4.demo使用验证packagecom.xing.design.flyweight.pool; publicclassConnectDemo { publicstaticvoidmain(String[] args) { System.out.println("--------------------------------mysql--------------------------------"); ConnectMysqlmysql1= (ConnectMysql) ConnectFacatory.getConnect("mysql"); mysql1.setUrl("http//127.0.0.1/xing"); mysql1.connect(); ConnectMysqlmysql2= (ConnectMysql) ConnectFacatory.getConnect("mysql"); mysql2.setUrl("http//127.0.0.2/hua"); mysql2.connect(); if(mysql1.hashCode() ==mysql2.hashCode()) { System.out.println("mysql1->hash码【"+mysql1.hashCode()+"】和mysql2->hash码【"+mysql1.hashCode()+"】是同一个对象"); } System.out.println("--------------------------------oracle--------------------------------"); ConnectOracleoracle1= (ConnectOracle) ConnectFacatory.getConnect("oracle"); oracle1.setUrl("http//127.0.0.1/ora"); oracle1.setUserName("root"); oracle1.setPassWord("123"); oracle1.connect(); ConnectOracleoracle2= (ConnectOracle) ConnectFacatory.getConnect("oracle"); oracle2.setUrl("http//127.0.0.1/ora"); oracle2.setUserName("root"); oracle2.setPassWord("root"); oracle2.connect(); ConnectOracleoracle3= (ConnectOracle) ConnectFacatory.getConnect("oracle"); oracle3.setUrl("http//127.0.0.1/jing"); oracle3.setUserName("cang"); oracle3.setPassWord("123"); oracle3.connect(); System.out.println("oracle1的hashCode:"+oracle1.hashCode()); System.out.println("oracle2的hashCode:"+oracle2.hashCode()); System.out.println("oracle3的hashCode:"+oracle3.hashCode()); System.out.println("--------------------------------mongoDB--------------------------------"); ConnectmongoDB=ConnectFacatory.getConnect("mongoDB"); if(mongoDB==null) { System.out.println("呐,mongoDB不支持"); } } }
结果:
OK!
总结:
享元模式就是将用过的对象用HashMap作为缓存池存储,下次用的时候用唯一的外部状态(type)去缓存池取,取不到再重新new,用这样的模式来大大减少相同对象的初始化,减少内存开支,缺点是提高了系统复杂度,核心代码是必须要有一个工厂类来控制对象的创建。
END