享元模式是什么?

享元模式(Flyweight Pattern)是一种结构型的设计模式,它的思想是摒弃在每个对象中保存所有数据的方式,通过共享多个对象共有的相同状态,使我们能够在有限的内存容量中更快地载入更多的对象。
相信大家都玩过超级马里奥这款游戏,游戏中马里奥的冒险之旅会碰到各种怪物的干扰,一旦马里奥被这些怪物碰触到,游戏就以失败结束了。游戏中会出现大量的怪物,每出现一个怪物时,我们就得创建一个对象来表示。游戏画面在渲染怪物时,得将表示该怪物的图片加载内存中,这个过程不仅耗时也很耗内存。
对于同一种怪物,比如蘑菇怪,渲染它们的图片是一样的并且是不会改变的。这种只能读取但不能修改的常量属性,我们称之为内在状态。而值会变化的属性,则称之为外在状态,比如怪物的坐标,它会随着怪物移动而变化。所以,我们其实可以将一个对象分成两个部分,包含内在状态的部分,以及包含外在状态的部分。仅包含内在状态的部分称之为享元
为了能方便地维护各种享元对象,我们可以创建一个缓存池来管理已有的享元对象。当要获取一个享元对象时,先从缓存池中查找,如果找到了,就将其返回;如果找不到,则新建一个享元对象,并将其添加到缓存池中。

案例

让我们通过模拟马里奥游戏来进一步理解享元模式。首先,我们定义一个 Monster 类来表示怪物。

  1. public class Monster {
  2. private MonsterType monsterType;
  3. private Position position;
  4. private Image image;
  5. public void render() {
  6. // Our code for rendering the monster
  7. }
  8. // other methods
  9. }

现在,我们知道 image 属性是 Monster 对象的内部状态,所以我们不会每创建一个 Monster 对象就跟着 new 一个 Image 对象。我们会用一个缓存池来管理 Image 对象,并提供一个工厂方法负责对象的创建、获取。

  1. public class ImageFactory {
  2. private static Map<String, Image> imageCache = new HashMap<String, Image>();
  3. public static Image createImage(String path) {
  4. Image image;
  5. if (imageCache.containsKey(path)) {
  6. image = imageCache.get(path);
  7. } else {
  8. image = new Image(path);
  9. imageCache.put(path, image);
  10. }
  11. return image;
  12. }
  13. }

为了更好地维护 Monster 对象的创建,我们同样也是提供一个工厂方法。在创建 Monster 对象时,image 享元对象是通过 ImageFactory 获取的,而不是 new 得到的。

  1. public class MonsterFactory {
  2. private static Map<MonsterType, String> monsterImagePathMap = new HashMap<>();
  3. static {
  4. monsterImagePathMap.put(MonsterType.MUSHROOM, "/images/monster/mushroom.png");
  5. monsterImagePathMap.put(MonsterType.TORTOISE, "/images/monster/tortoise.png");
  6. // ...
  7. }
  8. public static Monster createMonster(MonsterType monsterType, Position position) {
  9. Monster monster = new Monster();
  10. monster.setMonsterType(monsterType);
  11. monster.setPosition(position);
  12. Image image = ImageFactory.createImage(monsterImagePathMap.get(monsterType));
  13. monster.setImage(image);
  14. return monster;
  15. }
  16. }

案例源码

可在 GitHub 上查看上述案例的完整代码。

参考资料

本文参考的资料如下: