【设计模式-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);
    }
}

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

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

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

总结

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

相关推荐
Dcs17 分钟前
VSCode等多款主流 IDE 爆出安全漏洞!插件“伪装认证”可执行恶意命令!
java
保持学习ing23 分钟前
day1--项目搭建and内容管理模块
java·数据库·后端·docker·虚拟机
京东云开发者34 分钟前
Java的SPI机制详解
java
超级小忍1 小时前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
程序无bug1 小时前
Spring IoC注解式开发无敌详细(细节丰富)
java·后端
小莫分享1 小时前
Java Lombok 入门
java
程序无bug1 小时前
Spring 对于事务上的应用的详细说明
java·后端
食亨技术团队1 小时前
被忽略的 SAAS 生命线:操作日志有多重要
java·后端
苦学编程的谢1 小时前
Maven
java·maven·intellij-idea
考虑考虑1 小时前
Maven 依赖范围(Scope)
java·后端·maven