重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本篇内容主要讲解“Java的享元模式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java的享元模式是什么”吧!
站在用户的角度思考问题,与客户深入沟通,找到鹿城网站设计与鹿城网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:成都网站制作、成都网站建设、企业官网、英文网站、手机端网站、网站推广、申请域名、虚拟主机、企业邮箱。业务覆盖鹿城地区。
前言
试想一下,如果我们要用程序来设计围棋游戏,黑子181枚,白子180枚,那我们是不是每下一个子时,都要去new一个棋子对象呢?Java是一门面向对象语言,我们都知道如果在内存中不停的new新对象时,当对象数量太多时,又回收不及时时,将导致运行代价过高,带来性能下降等问题。那我们怎么去解决这类问题呢?下面,将为大家讲解本篇的重点,Java设计模式之——享元模式。
什么是享元模式
为了方便理解,我们先来看一下享元模式的两种状态:
内部状态(Intrinsic State):是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
外部状态(Extrinsic State):是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。
享元模式将一个对象的状态分为内部状态和外部状态,其中,二者是相互独立的,共享相同的内部状态,通过设置不同的外部状态来改变对象的特征,让一个对象拥有不同的特征,但内部状态始终是共享的,不可改变的。也就是,改变外部状态不会引起内部状态改变。
可以把围棋想象成享元模式,他们的大小、形状、颜色是内部状态,棋子的位置是外部状态,这样在设计时,只需要设置黑白棋子两个对象,黑棋共享黑色的内部状态,白棋共享白色的内部状态,棋盘上每个棋子的位置就是他们的外部状态,围棋盘361个交叉点位置,棋子每落一个位置(外部状态),都不会改变棋子的颜色(内部状态)。这样是不是好理解一点。
享元模式一般会结合工厂模式使用,目的是为了创建一个享元工厂来负责维护享元池(Flyweight Pool),享元池里存放的是具有相同内部状态的享元对象。在实际的日常业务的千变万化中,能够共享的内部状态是很少的,所以享元对象一般都设计为较小的对象,包含的内部状态也很少,这种对象也成为细粒度对象。
现在我们来看一下享元模式的英文定义:
Flyweight Pattern: Use sharing to support large numbers of fine-grained objects efficiently.
翻译过来就是:运用共享技术有效地支持大量细粒度对象的复用。(Flyweight我不也不懂为什么国内都翻译成享元,没找到资料,可能是根据这个模式的作用和特性翻译来的,如果有知道的朋友烦请文末留言告知一声,谢谢!)
再看一下国内对享元模式的解释:
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
简而言之:享元模式的目的就是通过共享不变的部分,达到减少对象数量并节约内存的目的。
享元模式的四个角色
Flyweight(抽象享元类):接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。
ConcreteFlyweight(具体享元类):实现了抽象享元类,其实例称为享元对象。必须是可共享的,需要封装享元对象的内部状态;。
UnsharedConcreteFlyweight(非共享具体享元类):非共享的享元实现对象,并不是所有的享元对象都可以共享,非共享的享元对象通常是享元对象的组合对象。
FlyweightFactory(享元工厂类):享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口。它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
享元模式的UML图
代码实例
我就不用我和李大爷下棋的例子了,以免在他老大(幼小)的心灵上留下创伤。关于棋子的案例,网上也有很多版本,大家感兴趣的可以自己去看。下面我们用王者荣耀游戏来举例。我们知道,在一局对战赛里,每隔几分钟就会出现一波小兵和超级兵,小兵都长的一模一样,超级兵也是,如果王者团队在设计小兵出场的时候,每出来一个小兵,就new一个小兵对象,那么在这个几百万甚至更多人同时在线角逐的游戏里,服务器压力根本就顶不住,还能不能好好的、流畅的、愉快的上分了,小学生放学后早就乖乖在家做作业了。
那么怎样设计呢?我们可以将小兵的体征、装配、兵种作为内部状态,然后它们在地图上出击的方向作为外部状态,这样无论小兵从哪个方向出击(外部状态怎样改变),都不会改变小兵的体征和兵种(内部状态),这样我们在开发时,每个兵种只要有一个享元对象就可以了。来看代码:
1、编写抽象享元类
package com.weiya.mazhichu.designpatterns.flyweight; /** ** 功能:抽象享元类 *
* * @author Moore * @ClassName Soldier flyweight. * @Version V1.0. * @date 2019.09.03 21:06:52 */ public interface SoldierFlyweight { /** ** 功能:敌军出击方法 *
* * @param direction : * @author Moore * @date 2019.09.03 21:06:52 */ public void attack(String direction); }
2、编写具体享元类
package com.weiya.mazhichu.designpatterns.flyweight; /** ** 功能:具体享元类 *
* * @author Moore * @ClassName Concrete solider flyweight. * @Version V1.0. * @date 2019.09.04 09:45:41 */ public class ConcreteSoliderFlyweight implements SoldierFlyweight { // 内部状态 private String soliderType; public ConcreteSoliderFlyweight(String soliderType) { this.soliderType = soliderType; } @Override public void attack(String direction) { if("normal".equals(soliderType)){ System.out.println("普通兵加入战场"); } if("super".equals(soliderType)){ System.out.println("超级兵加入战场"); } System.out.println("出击方向:"+direction); } }
3、编写享元工厂
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.Map; /** ** 功能:享元工厂 *
* * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */ public class SoldierFlyWeightFactory { //工厂实例 private static SoldierFlyWeightFactory INSTANCE; // 享元池 private static MapsoldierMap = new HashMap (); private SoldierFlyWeightFactory(){ SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal"); soldierMap.put("normal",normalSoldier); SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super"); soldierMap.put("super",superSolider); } /** * * 功能:获取工厂实例 *
* * @return soldier fly weight factory * @author Moore * @date 2019.09.03 21:07:02 */ public static SoldierFlyWeightFactory getInstance(){ if(INSTANCE == null){ INSTANCE = new SoldierFlyWeightFactory(); return INSTANCE; } return INSTANCE; } /** ** 功能:获取享元对象 *
* * @param soliderType : * @return soldier flyweight * @author Moore * @date 2019.09.03 21:07:02 */ public SoldierFlyweight getSolider(String soliderType){ return soldierMap.get(soliderType); } /** ** 功能:获取享元池对象数量 *
* * @return int * @author Moore * @date 2019.09.03 21:07:02 */ public int getSoliderSize(){ return soldierMap.size(); } }
4、客户端测试
package com.weiya.mazhichu.designpatterns.flyweight; /** ** 功能: *
* * @author Moore * @ClassName Honour of kings test. * @Version V1.0. * @date 2019.09.03 21:06:44 */ public class HonourOfKingsTest { public static void main(String[] args) { System.out.println("敌军还有五秒到达战场!"); SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance(); SoldierFlyweight soldier1 = factory.getSolider("normal"); SoldierFlyweight soldier2 = factory.getSolider("normal"); SoldierFlyweight soldier3 = factory.getSolider("normal"); soldier1.attack("上路"); soldier2.attack("中路"); soldier3.attack("下路"); System.out.println(soldier1 == soldier2); System.out.println(soldier2 == soldier3); System.out.println("--------------------------"); System.out.println("主宰已被击败!"); SoldierFlyweight soldier4 = factory.getSolider("super"); SoldierFlyweight soldier5 = factory.getSolider("super"); SoldierFlyweight soldier6 = factory.getSolider("super"); soldier4.attack("上路"); soldier5.attack("中路"); soldier6.attack("下路"); System.out.println("对方法师残血,被超级兵打死..."); System.out.println(soldier4 == soldier5); System.out.println(soldier5 == soldier6); System.out.println("--------------------------"); System.out.println("该案例一共生成对象:">
查看运行结果:
可以看出,我们一共派出了6个小兵,其中3个普通兵,3个超级兵,但是享元池中只有两个对象(一个普通兵、一个超级兵对象),也就是说,无论派出多少普通兵或者超级兵,无论它们要从哪一路出击,都不会影响兵的内部状态,从而让整个系统的对象大大减少,减少内存消耗,不卡就不影响游戏体验,小学生又可以开心快乐的出来坑人了,但是要以学业为重哦!
享元模式扩展
在上面的实例中,我们主要讲的是具体的享元对象,也就是所有的享元对象都是必须共享的。但是享元模式的四个角色中还有一个非共享的享元实现对象,什么意思呢,顾名思义就是享元对象不一定要共享,但是它通常是作为享元对象的组合对象来使用。从这个层面来说,我们又把享元对象分为:
单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。(复合的享元对象实现了抽象享元类,它的实例就是非共享的享元实现对象)
复合享元模式中,组成复合享元对象的每个单纯享元对象拥有自己的内部状态,而每个单纯享元对象的外部状态都和复合享元对象的外部状态相同。所以复合享元模式可以对多个单纯享元对象设置相同的外部状态, 这也是复合享元模式的应用场景。
单纯的享元模式我就不再赘述了,看上面的棋子或者农药的实例,下面主要说一下组合享元模式,以及它为何非共享,来看代码:
1、编写复合享元角色类
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.Map; /** ** 功能: 复合享元角色类(非共享享元实现对象) *
* * @author Moore * @ClassName Concrete composite solider flyweight. * @Version V1.0. * @date 2019.09.04 10:56:11 */ public class ConcreteCompositeSoliderFlyweight implements SoldierFlyweight { private static MapsoldierMap = new HashMap (); /** * * 功能: 增加单纯享元对象 *
* * @param soliderType : * @param flyweight : * @author Moore * @date 2019.09.04 10:56:11 */ public void add(String soliderType,SoldierFlyweight flyweight){ soldierMap.put(soliderType,flyweight); } /** ** 功能: flyWeights是单纯享元对象的集合,它们具有相同的外部状态extrinsicState, * 调用的时候使用循环调用单纯享元对象的attack方法 *
* * @param direction : * @author Moore * @date 2019.09.03 21:06:52 */ @Override public void attack(String direction) { SoldierFlyweight flyweight = null; for(String str : soldierMap.keySet()){ flyweight = soldierMap.get(str); flyweight.attack(direction); } } /** * 移除单纯享元对象. * @param soliderType */ private void remove(String soliderType) { soldierMap.remove(soliderType); } }
2、修改后的享元工厂角色类
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.List; import java.util.Map; /** ** 功能:享元工厂 *
* * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */ public class SoldierFlyWeightFactory { //工厂实例 private static SoldierFlyWeightFactory INSTANCE; // 享元池 private static MapsoldierMap = new HashMap (); private SoldierFlyWeightFactory(){ SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal"); soldierMap.put("normal",normalSoldier); SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super"); soldierMap.put("super",superSolider); } /** * * 功能:获取工厂实例 *
* * @return soldier fly weight factory * @author Moore * @date 2019.09.03 21:07:02 */ public static SoldierFlyWeightFactory getInstance(){ if(INSTANCE == null){ INSTANCE = new SoldierFlyWeightFactory(); return INSTANCE; } return INSTANCE; } /** ** 功能:获取享元对象(单纯享元工厂方法) *
* * @param soliderType : * @return soldier flyweight * @author Moore * @date 2019.09.03 21:07:02 */ public SoldierFlyweight getSolider(String soliderType){ return soldierMap.get(soliderType); } /** ** 功能:复合享元工厂方法 *
* * @param compositeSoliderTypes : * @return soldier flyweight * @author Moore * @date 2019.09.04 11:06:24 */ public SoldierFlyweight getCompositeSolider(ListcompositeSoliderTypes){ ConcreteCompositeSoliderFlyweight compositeFlyweight = new ConcreteCompositeSoliderFlyweight(); for(String soliderType : compositeSoliderTypes){ compositeFlyweight.add(soliderType,this.getSolider(soliderType)); } return compositeFlyweight; } /** * * 功能:获取享元池对象数量 *
* * @return int * @author Moore * @date 2019.09.03 21:07:02 */ public int getSoliderSize(){ return soldierMap.size(); } }
3、编写测试类
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.ArrayList; import java.util.List; /** ** 功能: 测试单纯享元模式和复合享元模式 *
* * @author Moore * @ClassName Flyweight test. * @Version V1.0. * @date 2019.09.04 11:08:51 */ public class FlyweightTest { public static void main(String[] args) { SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance(); String soliderType = "normal"; SoldierFlyweight soldierFlyweight1 = factory.getSolider(soliderType); SoldierFlyweight soldierFlyweight2 = factory.getSolider(soliderType); soldierFlyweight1.attack("上路"); soldierFlyweight2.attack("中路"); System.out.println("---------------------------------"); ListcompositeSoliderType = new ArrayList (); compositeSoliderType.add("normal"); compositeSoliderType.add("super"); compositeSoliderType.add("normal"); compositeSoliderType.add("super"); compositeSoliderType.add("normal"); SoldierFlyweight compositeSoliderFlyeweight1 = factory.getSolider(compositeSoliderType); SoldierFlyweight compositeSoliderFlyeweight2 = factory.getSolider(compositeSoliderType); compositeSoliderFlyeweight1.attack("上路"); compositeSoliderFlyeweight2.attack("中路"); System.out.println("---------------------------------"); System.out.println("单纯享元模式是否共享对象:">
查看运行结果:
结合运行结果,再来逐字逐句看一下这一段,你应该就能有所体会了。
复合享元模式中,组成复合享元对象的每个单纯享元对象拥有自己的内部状态,而每个单纯享元对象的外部状态都和复合享元对象的外部状态相同。所以复合享元模式可以对多个单纯享元对象设置相同的外部状态, 这也是复合享元模式的应用场景。
复合享元模式UML图
享元模式总结
使用场景
系统有大量相似或者相同对象。由于这类对象的大量使用,造成内存的大量耗费。
需要缓冲池的场景,(享元池,也就是在需要多次使用享元对象的时候)。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
优点
大大减少对象的创建,降低系统的内存,使效率提高。
享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点
需要分离出外部状态和内部状态,提高了系统的复杂度。
读取享元模式的外部状态会使得运行时间稍微变长。
到此,相信大家对“Java的享元模式是什么”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!