【游戏设计模式】单例模式全解析---Cocos Creater实操

前言

七大原则:

  • 单一职责原则(Single Responsibility Principle);

  • 开闭原则(Open Closed Principle);

  • 里氏替换原则(Liskov Substitution Principle);

  • 迪米特法则(Law of Demeter);

  • 接口隔离原则(Interface Segregation Principle);

  • 依赖倒置原则(Dependence Inversion Principle);

  • 合成复用原则(Composite Reuse Principle);

只有一个目的:

让程序员可以更好的偷懒,让所写过的代码可以应对各种各样的需求。

定义:

单例模式是一种创建型 设计模式,它确保一个类只有一个实例 ,并提供了一个全局访问点来访问该实例。

应用场景:

全局只想要一个实例对象,避免多个实例对象会存在数据不统一等情况下使用,具体场景如下:

  1. 全局游戏管理-GameManager:管理游戏运行时的状态

  2. 全局音频管理-AudioManager:管理游戏运行时的背景音乐、音效、人声等

  3. 全局数据代理-ProxyManager:管理游戏数据

例如全局音频管理播放了一个BGM,如果存在多个音频管理,就会导致多个BGM存在,一般情况游戏只会有一个BGM,因此为了避免这种情况,才使用单例模式进行设计与开发。

基本原理:

将类的初始化改成私有,只能通过自身进行初始化,这样就避免其他地方初始化多个对象出来。

typescript 复制代码
import { _decorator } from 'cc';
const { ccclass } = _decorator;

@ccclass('GameManager')
export class GameManager {
    private static _instance: GameManager = null;

    public static get Instance() {
        // 只能通过自身进行初始化
        if (this._instance == null) {
            this._instance = new GameManager();
        }

        return this._instance;
    }

    // 类的初始化改为私有
    private constructor() {}
}

具体案例:

  1. 简易计数器

场景布局如下:

具体代码如下:

typescript 复制代码
# GameManager.ts
import { _decorator } from 'cc';
const { ccclass } = _decorator;

// 全局游戏管理
@ccclass('GameManager')
export class GameManager {
    private static _instance: GameManager = null;

    public count: number = 0;

    // 只能通过自身进行初始化
    public static get Instance() {
        if (this._instance == null) {
            this._instance = new GameManager();
        }

        return this._instance;
    }

    // 类的初始化改为私有
    private constructor() {

    }
}

# SingletonExampleScene.ts
import { _decorator, Component, Node, Button, Label } from 'cc';
import { GameManager } from './GameManager';
const { ccclass, property } = _decorator;

@ccclass('SingletonExampleScene')
export class SingletonExampleScene extends Component {
    @property(Button)
    private AddButton: Button = null;

    @property(Button)
    private ReduceButton: Button = null;

    @property(Label)
    private textLabel: Label = null;

    public onLoad(): void {
        this.AddButton.node.on(Button.EventType.CLICK, this.OnClick_AddButton, this);
        this.ReduceButton.node.on(Button.EventType.CLICK, this.OnClick_ReduceButton, this);

        this.RefreshTextLabel();
    }

    private OnClick_AddButton(button: Button): void {
        GameManager.Instance.count++;
        this.RefreshTextLabel();
    }

    private OnClick_ReduceButton(button: Button): void {
        GameManager.Instance.count--;
        this.RefreshTextLabel();
    }

    private RefreshTextLabel(): void {
        this.textLabel.string = GameManager.Instance.count.toString();
    }
}

效果如下:

实现方式:

这里暂时只讲两种类型(不考虑线程安全,游戏中多线程的情况比较少),分别是提前初始化和延迟初始化

饿汉式-提前初始化

以GameManager为例,该实现方式会在程序启动时在静态存储区初始化GameManager中的静态变量_instance。

typescript 复制代码
import { _decorator } from 'cc';
const { ccclass } = _decorator;

@ccclass('GameManager')
export class GameManager {
    private static _instance: GameManager = new GameManager();

    public static get Instance() {
        return this._instance;
    }

    private constructor() {

    }
}

优点(不考虑线程安全):无

缺点:

  1. 程序启动时就直接全部初始化完成,占用大量内存,有可能有些单例对象从头到尾都没使用过,却一直占据程序内存。

懒汉式-延迟初始化

以GameManager为例,该实现方式只在第一次调用时初始化GameManager中的静态变量_instance。

typescript 复制代码
import { _decorator } from 'cc';
const { ccclass } = _decorator;

@ccclass('GameManager')
export class GameManager {
    private static _instance: GameManager = null;

    public static get Instance() {
        // 只能通过自身进行初始化
        if (this._instance == null) {
            this._instance = new GameManager();
        }

        return this._instance;
    }

    // 类的初始化改为私有
    private constructor() {}
}

优点:

  1. 调用才会初始化,不会一开始就占据程序内存。

缺点(不考虑线程安全):无

总结:

优点:

  1. 在内存中只有一个实例对象,不会过多创建对象,保证数据统一性。
  2. 避免对资源的多重占用。

缺点:

  1. 由于不能多态(进行继承),导致可拓展性差,所有功能都只能写在一个类统一管理。

题外话:

作者打算实现TypeScript的泛型单例模式时,查阅资料发现,TypeScript无法像C#那样简单的写出泛型单例模式。

C#实现泛型单例代码如下:

csharp 复制代码
public class Singleton<T> where T : class, new()
{
    private static T s_instance;
    
    // 类的初始化改为私有或保护
    protected Singleton()
    {
    }

    public static T Instance
    {
        get
        {
            if (Singleton<T>.s_instance == null)
            {
                Singleton<T>.CreateInstance();
            }
            return Singleton<T>.s_instance;
        }
    }
}

// 使用方式
GameManager.Instance.Count++;

TypeScript实现泛型单例代码如下:

typescript 复制代码
export class Singleton<T>{
    private static _instance: any = null;

    protected constructor() {}

    public static GetInstance<T>(target: { new(): T }): T {
        if (this._instance == null) {
            this._instance = new target();
        }
        return this._instance;
    }
}

// 使用方式
GameManager.Instance<GameManager>(GameManager).Count++;

很明显TypeScript的写法非常的冗余,没有人会选择此写法。

相关推荐
Zane19941 小时前
工厂方法一定比简单工厂高级?三种工厂模式到底该怎么选
设计模式
Hespethorn1 小时前
设计模式是语言缺陷的补丁 —— 从工厂、单例、策略三个模式说起
设计模式
Lost of 程序猿1 小时前
命令模式实战:把“下单后的动作“打包成可执行的任务
后端·设计模式·c#·asp.net·命令模式
一只旭宝2 小时前
【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)
c++·redis·笔记·mysql·设计模式
YYYing.3 小时前
【设计模式系列 (四) 】建造者模式
c++·后端·设计模式·建造者模式·c/c++
Zane199421 小时前
同一段单例代码,为什么在高并发下偶尔会创建出两个实例?聊聊哪种单例实现才是真的安全
设计模式
Carl_奕然21 小时前
【智能体】Loop 的四种设计模式之:Agent Loop(2026 最新版)
人工智能·设计模式·语言模型
小酒星小杜1 天前
画 AI 漫画,别只会写“日漫风”:10 种画风、适用故事和可复制提示词
人工智能·设计模式·程序员
sarasuki1 天前
为什么 Agent 工具需要一个「协议」而不是「框架」:MCP 成为标准的底层逻辑
设计模式·agent·mcp
vivo互联网技术1 天前
知识不是文件,也不是向量 | KDC 系列 02
人工智能·设计模式·架构