享元模式--大量的飞龙
作者:互联网
文章目录
引子
小帅就职于一家游戏公司,参与开发一款RPG游戏,他负责设计游戏里的怪物。有一个场景是玩家冲进了怪物的老巢,里面有成百上千的飞龙,成群的飞龙向玩家冲过来,这时候游戏画面就会出现卡顿,体验非常不好。领导让小帅优化一下这个场景,让游戏体验变的丝滑如水。
原来的代码如下:
怪物类:
/**
* 怪物类
*/
public class Monster {
public static enum Color {
RED, BLACK;
}
/**
* 名称
*/
private String name;
/**
* 技能
*/
private String skill;
/**
* 颜色
*/
private Color color;
/**
* 其他数据
*/
private String otherData;
/**
* x坐标
*/
private int x;
/**
* y坐标
*/
private int y;
public Monster(String name, String skill, Color color, String otherData, int x, int y) {
this.name = name;
this.skill = skill;
this.color = color;
this.otherData = otherData;
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "怪物名称:" + name + ",技能:" + skill + ",颜色:" + color + ", 其他数据:" + otherData + ",x坐标:" + x + ",y坐标:" + y;
}
}
游戏场景类:
/**
* 游戏场景
*/
public class GameScene {
public static void main(String[] args) {
List<Monster> monsterList = new ArrayList<Monster>();
Random rand = new Random();
for(int i = 0; i < 1000; i++) {
monsterList.add(new Monster("飞龙","空袭", Monster.Color.BLACK, "其他数据", rand.nextInt(1000), rand.nextInt(1000)));
}
for(Monster monster : monsterList) {
System.out.println(monster);
}
}
}
一部分输出:
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:578,y坐标:636
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:40,y坐标:44
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:309,y坐标:250
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:301,y坐标:405
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:945,y坐标:35
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:192,y坐标:625
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:136,y坐标:852
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:158,y坐标:853
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:290,y坐标:29
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:454,y坐标:76
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:901,y坐标:910
小帅发现是飞龙太多,占用的内存太大,导致卡顿,有没有一种节省内存的方法呢?
仔细观察一下飞龙对象,飞龙只有两种,一种是红色,另一种是黑色,其他的属性都是一样的,还有就是它们在地图上的坐标不同。我们能不能复用相同的飞龙数据,只是把不同位置飞龙的坐标独立开来呢?
刚好有个设计模式就是解决这个问题的。
享元模式
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。 系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
(图片来源:https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/flyweight.html)
- Flyweight:抽象享元类
- ConcreteFlyweight:共享具体享元类,ConcreteFlyweight对象必须是可共享的,它所存储的状态必须是内部的,不可变的。
- UnsharedConcreteFlyweight: 非共享具体享元类
- FlyweightFactory:享元工厂类,当用户请求一个flyweight对象时,FlyweightFactory对象提供一个实例,如果实例已经创建过就直接拿来用,如果没创建过,就新建一个实例。
- Client:维持一个对flyweight对象的引用;计算或存储一个(多个)flyweight的外部状态。
由于飞龙都长一样,成群的飞龙冲过来,只是它们在地图上的位置不同而已。享元模式的要点是找出对象中不变的部分(飞龙单位)和变化的部分(坐标),把它们区分开来,复用不变的部分,达到节省内存的目的。
不可变对象,我们就可以利用享元模式将对象设计成享元(飞龙单位),在内存中只保留一份实例,然后再和坐标组合起来就是一只活蹦乱跳的飞龙啦。
重构后的代码如下:
怪物单位类:
**
* 怪物单位
*/
public class MonsterUnit {
protected static enum Color {
RED, BLACK;
}
/**
* 名称
*/
protected String name;
/**
* 技能
*/
protected String skill;
/**
* 颜色
*/
protected Color color;
/**
* 其他数据
*/
protected String otherData;
public MonsterUnit(String name, String skill, Color color, String otherData) {
this.name = name;
this.skill = skill;
this.color = color;
this.otherData = otherData;
}
}
怪物单位工厂类:
/**
* 怪物单位工厂
*/
public class MonsterUnitFactory {
private static final Map<String, MonsterUnit> monsters = new HashMap<>();
/**
* 获取怪物单位
* @param name
* @param skill
* @param color
* @param otherData
* @return
*/
public static MonsterUnit getMonsterUnit(String name, String skill, MonsterUnit.Color color, String otherData) {
// 用怪物的名称和颜色作为map的key,每种颜色的怪物复用一个对象
MonsterUnit monsterUnit = monsters.get(name + color);
if(monsterUnit == null) {
monsterUnit = new MonsterUnit(name, skill, color, otherData);
monsters.put(name + color, monsterUnit);
}
return monsterUnit;
}
}
怪物类:
/**
* 怪物
*/
public class Monster {
/**
* 怪物单位
*/
private MonsterUnit monsterUnit;
/**
* x坐标
*/
private int x;
/**
* y坐标
*/
private int y;
public Monster(MonsterUnit monsterUnit, int x, int y) {
this.monsterUnit = monsterUnit;
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "怪物名称:" + monsterUnit.name + ",技能:" + monsterUnit.skill + ",颜色:" + monsterUnit.color + ", 其他数据:" + monsterUnit.otherData + ",x坐标:" + x + ",y坐标:" + y;
}
}
游戏场景类:
/**
* 游戏场景
*/
public class GameScene {
public static void main(String[] args) {
List<Monster> monsterList = new ArrayList<Monster>();
Random rand = new Random();
// 怪物
Monster monster;
// 红色怪物单位
MonsterUnit redMonsterUnit;
// 黑色怪物单位
MonsterUnit blackMonsterUnit;
// 获取红色怪物单位
redMonsterUnit = MonsterUnitFactory.getMonsterUnit("飞龙","空袭", MonsterUnit.Color.RED , "其他数据");
// 获取黑色怪物单位
blackMonsterUnit = MonsterUnitFactory.getMonsterUnit("飞龙","空袭", MonsterUnit.Color.BLACK, "其他数据");
for(int i = 0; i < 1000; i++) {
// 怪物单位加上坐标生产怪物对象
monster = new Monster(rand.nextInt() > 0 ? redMonsterUnit : blackMonsterUnit, rand.nextInt(1000), rand.nextInt(1000));
monsterList.add(monster);
}
for(Monster item : monsterList) {
System.out.println(item);
}
}
}
输出:
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:874,y坐标:57
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:754,y坐标:692
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:396,y坐标:535
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:653,y坐标:281
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:649,y坐标:995
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:195,y坐标:94
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:678,y坐标:921
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:571,y坐标:743
怪物名称:飞龙,技能:空袭,颜色:RED, 其他数据:其他数据,x坐标:631,y坐标:271
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:794,y坐标:634
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:492,y坐标:973
怪物名称:飞龙,技能:空袭,颜色:BLACK, 其他数据:其他数据,x坐标:485,y坐标:46
这里我们只是创建了两个飞龙单位,一个红色的飞龙和一个黑色的飞龙,把它们放到Map里,通过工厂类获取。
Monster(怪物类)复用了MonsterUnit(怪物单位),这里只是对MonsterUnit对象的引用,是不变的部分,这种不可变的状态叫做内部状态;而坐标是动态变化的,每个对象都不一样,这种可变的状态叫做外部状态。
应用享元模式的要点是要把类的成员变量拆分为两个部分:
- 内部状态: 包含不变的、 可在许多对象中重复使用的成员变量。
- 外部状态: 包含每个对象各自不同的场景的成员变量。
内部状态存储于ConcreteFlyweight对象中,而外部状态则由Client对象存储或计算。当用户调用flyweight对象的操作时,要将外部状态传递给它。
如果外部状态可以计算出来,不用传递进来,那么就可以做到用时间换空间,让节省的空间最大化。
总结
所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。具体来讲,当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,以起到节省内存的目的。
享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个Map 或者List 来缓存已经创建好的享元对象,以达到复用的目的。
优点
- 如果程序中有很多相似对象, 那么你将可以节省大量内存。
缺点
- 代码会变得更加复杂,需要拆分内部状态和外部状态。
享元模式和单例模式的不同
- 在享元模式中,一个类可以创建多个对象;而在单例模式中,一个类只能创建一个对象。
- 享元模式的意图是为了复用对象,节省内存;单例模式的意图是为了控制对象的个数。
标签:享元,飞龙,空袭,--,怪物,其他,数据,坐标 来源: https://blog.csdn.net/zhanyd/article/details/118222904