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

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

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

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

总结

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

相关推荐
Chen-Edward19 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
陈小桔20 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!20 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg367820 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July21 小时前
Hikari连接池
java
微风粼粼21 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad21 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情67321 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术21 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie1 天前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi