其他分享
首页 > 其他分享> > 享元模式--大量的飞龙

享元模式--大量的飞龙

作者:互联网

文章目录

引子

小帅就职于一家游戏公司,参与开发一款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)

由于飞龙都长一样,成群的飞龙冲过来,只是它们在地图上的位置不同而已。享元模式的要点是找出对象中不变的部分(飞龙单位)和变化的部分(坐标),把它们区分开来,复用不变的部分,达到节省内存的目的。

不可变对象,我们就可以利用享元模式将对象设计成享元(飞龙单位),在内存中只保留一份实例,然后再和坐标组合起来就是一只活蹦乱跳的飞龙啦。

在这里插入图片描述

重构后的代码如下:

怪物单位类:

**
 * 怪物单位
 */
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