【设计模式-3.3】结构型——享元模式

说明:说明:本文介绍设计模式中结构型设计模式中的,享元模式;

游戏地图

在一些闯关类的游戏,如超级玛丽、坦克大战里面,游戏的背景每一个关卡都不相同,但仔细观察可以发现,其都是用一些基础图标组成的,背景的变化实际上就是改变了其基础图标的位置。

如坦克大战,游戏背景实际上就是通过敌方坦克、我方坦克、砖墙、铁墙、海洋、草丛、基地等这些图标组成的。

基础图标,坦克、草地、河流......

现在,如果要我们来开发这样一个游戏地图。首先,我们会创建一个地图块类,如下:

(Tile,图块类)

java 复制代码
/**
 * 图块类
 */
public class Tile {

    /**
     * 图块名称
     */
    private String image;

    /**
     * 图块的x坐标
     */
    private int x;

    /**
     * 图块的y坐标
     */
    private int y;

    /**
     * 创建图块对象
     */
    public Tile(String image, int x, int y) {
        this.image = image;
        this.x = x;
        this.y = y;
        System.out.println("加载图片:" + image);
    }

    /**
     * 绘制图块
     */
    public void draw() {
        System.out.println("绘制图片:" + image + ",坐标:" + x + "," + y);
    }
}

(Client,客户端,演示游戏地图创建过程)

java 复制代码
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 在地图上绘制两个"海洋"图块
        new Tile("海洋", 1, 2).draw();
        new Tile("海洋", 2, 3).draw();

        // 在地图上绘制两个"草丛"图块
        new Tile("草丛", 1, 3).draw();
        new Tile("草丛", 2, 4).draw();

        // 在地图上绘制两"砖墙"图块
        new Tile("砖墙", 1, 4).draw();
        new Tile("砖墙", 2, 5).draw();
    }
}

运行程序,加载游戏地图

分析以上过程,会发现问题很大。每次创建一个图块对象,都需要new,加载时间特别长,且浪费资源。

为了解决这个问题,我们似乎可以考虑用原型设计模式解决。但是我们需要考虑,对于一个游戏地图来说,图块种类的数量可能是非常庞大的,使用原型模式对对象进行克隆,会有大量的内存开销,如果没有良好的内存回收机制,可能会导致内存溢出,造成系统崩溃。因此,使用原型模式来解决游戏地图的设计,不一定是合适的。

享元模式

享元,元指的是最小的单元,也就是上面坦克大战中草丛、海洋这些最小的图块,享元就是共享这些最小图块。通过享元模式对上面地图加载代码改进,如下:

(Drawable,绘画接口)

java 复制代码
/**
 * 绘画接口
 */
public interface Drawable {
    
    /**
     * 绘制图块
     */
    void draw(int x, int y);
}

(Grass,草地图块)

java 复制代码
/**
 * 草地图块
 */
public class Grass implements Drawable {

    /**
     * 图块名称
     */
    private String name;

    public Grass() {
        this.name = "草地";
        System.out.println("创建图块:" + name);
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("绘制【" + name + "】图块:" + name + ",位置:(" + x + ", " + y + ")");
    }
}

(Wall,墙壁图块)

java 复制代码
/**
 * 墙壁图块
 */
public class Wall implements Drawable {

    /**
     * 图块名称
     */
    private String name;

    public Wall() {
        this.name = "墙壁";
        System.out.println("创建墙壁图块:" + name);
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("绘制【" + name + "】图块:" + name + ",位置:(" + x + ", " + y + ")");
    }
}

(River,河流图块)

java 复制代码
/**
 * 河流图块
 */
public class River implements Drawable {

    /**
     * 图块名称
     */
    private String name;

    public River() {
        this.name = "河流";
        System.out.println("创建图块:" + name);
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("绘制【" + name + "】图块:" + name + ",位置:(" + x + ", " + y + ")");
    }
}

(Home,基地图块)

java 复制代码
/**
 * 基地图块
 */
public class Home implements Drawable {

    /**
     * 基地图块名称
     */
    private String name;

    public Home() {
        this.name = "基地";
        System.out.println("创建图块:" + name);
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("绘制【" + name + "】图块:" + name + ",位置:(" + x + ", " + y + ")");
    }
}

(TileFactory,图块工厂)

java 复制代码
import java.util.HashMap;
import java.util.Map;

/**
 * 图块工厂
 */
public class TileFactory {

    /**
     * 图块集合
     */
    private Map<String, Drawable> Tiles;

    /**
     * 图块工厂构造函数
     */
    public TileFactory() {
        Tiles = new HashMap<String, Drawable>();
    }

    /**
     * 根据图块名称获取图块,没有就创建并返回,有就直接从集合中获取并返回
     */
    public Drawable getTile(String name) {
        // 没有就创建
        if (!Tiles.containsKey(name)) {
            switch (name) {
                case "河流":
                    Tiles.put(name, new Wall());
                    break;
                case "草地":
                    Tiles.put(name, new Grass());
                    break;
                case "基地":
                    Tiles.put(name, new Home());
                    break;
                case "墙壁":
                    Tiles.put(name, new Wall());
                    break;
                default:
                    break;
            }
        }
        // 有就直接返回
        return Tiles.get(name);
    }
}

(Client,客户端,演示游戏地图加载过程)

java 复制代码
/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 创建图块工厂
        TileFactory tileFactory = new TileFactory();

        // 绘制地图
        tileFactory.getTile("草地").draw(0, 0);
        tileFactory.getTile("草地").draw(1, 0);
        tileFactory.getTile("草地").draw(2, 0);

        tileFactory.getTile("河流").draw(0, 1);
        tileFactory.getTile("河流").draw(1, 1);

        tileFactory.getTile("墙壁").draw(0, 2);
        tileFactory.getTile("墙壁").draw(1, 2);

        tileFactory.getTile("基地").draw(0, 3);
    }
}

可以发现,各个图块构造器内的输出语句只执行了一次,说明后续图块的创建没有调用其构造方法,实现了享元

以上就是通过享元模式对游戏地图设计的过程,实际上就是对最小单元对象的共享,使其不重复去创建对象。

需要注意区分于工厂设计模式,工厂模式是创建型设计模式,关注对象的创建,而享元模式是对已创建对象的一种利用,是结构型设计模式。

总结

本文参考《设计模式的艺术》、《秒懂设计模式》两书

相关推荐
让学习成为一种生活方式15 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画21 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生44 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink1 小时前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea
小冉在学习2 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论
代码之光_19802 小时前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi2 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet