当前位置: 首页 > article >正文

Unity 设计模式-单例模式(Singleton)详解

设计模式

设计模式 是指在软件开发中为解决常见问题而总结出的一套 可复用的解决方案。这些模式是经过长期实践证明有效的 编程经验总结,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的 抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。

设计模式总共有23种,总体来说可以分为三大类:创建型模式(Creational Patterns)、结构型模
式(Structural Patterns)和行为型模式(Behavioral Patterns)。
创建型模式:工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式关注于对象的创建,同时隐藏创建逻辑
结构型模式:适配器模式、过滤器模式、装饰模式、元模式、代理模式、外观模式、组合模式、桥接模式关注类和对象之间的组合
行为型模式:责任链模式、命令模式、中介者模式、观察者模式、状态模式、策略模式、模板模式、空对象模式、忘录模式、迭代器模式、解释器模式、访问者模式关注对象之间的通信

设计模式的特点:

  • 通用性:设计模式针对的是软件开发中常见的设计问题,适用于各种软件工程项目。
  • 可复用性:设计模式可以在不同项目和环境下被重复使用,提高代码的可维护性和扩展性。
  • 可扩展性:设计模式有助于让代码结构更加灵活,易于扩展和修改。
  • 模块化:通过设计模式,可以减少代码的耦合性,增强模块间的独立性。
  • 提高沟通效率:设计模式为开发者提供了一种通用的设计语言,使得团队成员能够快速理解并讨论设计方案。

单例模式(Singleton)

单例模式 (Singleton Pattern) 是一种创建型设计模式,保证一个类只有一个实例,并提供一个全局访问点来获取该实例。它通过控制类的实例化过程,确保系统中只有一个该类的对象存在。

在单例模式中,类的构造函数通常是私有的,防止外部通过 new 来创建对象,类内部维护一个静态实例,通过公共的静态方法提供访问。

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景:由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

总之,单例模式 主要用于控制对象的实例化,确保系统中只有一个类的实例,并通过全局访问点来控制对象的使用。它适用于需要全局共享资源、统一管理的场景,如日志系统、数据库连接池等。尽管单例模式在某些场景下有助于提升系统的稳定性和效率,但也应谨慎使用,以避免全局状态管理复杂化或滥用全局访问带来的耦合问题。

在 Unity 中使用 单例模式

在 Unity 中,实现一个线程安全的普通类MonoBehaviour 类的泛型单例时,必须考虑以下几点:

  1. 普通类单例:不能被 new,并且在多线程环境下线程安全。
  2. MonoBehaviour 单例:由于 MonoBehaviour 的实例是通过 Unity 的 AddComponent 创建的,不能直接通过 new,也需要保证在多线程环境下的安全性。
普通型(new)泛型单例模式
public abstract class Singleton<T> where T : class, new()
{
    private static T _instance = null;
 
    // 多线程安全机制
    private static readonly object locker = new object();
 
    public static T Instance
    {
        get
        {
            lock (locker)
            {
                if (_instance == null)
                    _instance = new T();
                return _instance;
            }
        }
    }
}

MonoBehaviour 的泛型单例模式
 私有构造函数

为了防止在类外部创建新的实例,将构造函数设为私有,这样其他类就不能直接通过  new  关键字来实例化该类。

using UnityEngine;

public class Singleton : MonoBehaviour
{
 
    private static Singleton _instance;
 
    // 私有构造函数
    private Singleton() { }
 
    // 静态访问方法
    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            _instance = this;
        }
        return instance;
    }
    // 可选:添加其他功能和数据到这个单例类
 
}
静态字段访问

如果我们想直接使用  instance  这个变量,我们可以将  instance  定义为公共字段而不是属性。这样,在其他脚本中就可以直接通过  GameManager.instance  来访问它。

public class GameManager : MonoBehaviour 
{

    public static GameManager _instance;
 
    private void Awake()
    {
        if(_instance!=null)
        {
            Destroy(gameObject);
        }
        else
        {
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }
    }
    public void Walk()
    {
        // 实现 Walk 方法的代码
    }
}

在上面的例子中,  GameManager  类的  instance  字段被定义为公共静态。在  Awake()  方法中,如果  instance  为  null  ,则将当前实例赋值给  instance  ,否则销毁重复的实例。这样,我们就可以在其他脚本中通过  GameManager.instance  来访问  GameManager  的唯一实例。

单例使用方式
GameManager.instance.Walk();

这样就可以直接调用  Walk()  方法而无需加括号。请注意,使用这种方式时,确保在调用  GameManager.instance  之前,  GameManager  类的实例已经被正确初始化。

两种方法比较

两种方法各有优缺点,取决于我们的需求和项目的规模。让我们来比较一下:

(1)使用静态方法:

优点:

     易于理解和维护:使用  GetInstance()  等明确的静态方法,可以清楚地表明我们正在获取单例实例。

     更好的封装:通过静态方法,可以对实例创建的逻辑进行更好的封装,确保在获取实例时进行一些初始化或其他操作。

     更安全:可以更好地控制实例的创建过程,避免因不当的直接访问导致的意外行为。

缺点:

     冗余代码:在使用单例的时候,可能需要多次写  GetInstance()  方法调用,造成一定程度的代码冗余。

(2)使用公共静态字段:

优点:

     简洁:直接使用  GameManager.instance  来访问单例实例,代码更加简洁明了。

     减少方法调用:省略了调用静态方法的过程,直接使用字段访问。

缺点:

     可读性和维护性较差:在代码中,我们无法清楚地看出  instance  是来自单例模式的,初次阅读代码可能会不太容易理解。

     可能不够安全:由于没有封装的控制,其他代码可能会直接修改或重置  instance  ,可能导致单例实例状态的不稳定。

综上所述,如果我们更关注代码的可读性、维护性和安全性,推荐使用静态方法来获取单例实例。这种方式使代码更具意图,并且允许在获取实例时进行更好的封装和控制。

如果我们更看重代码的简洁性,并且确认在项目中不会出现意外的直接修改  instance  的情况,使用公共静态字段可能会更加方便。

不管选择哪种方式,确保单例的创建和初始化逻辑是正确的,并且在使用单例实例时要小心避免潜在的错误和异常。

今天是2024年11月23日

重复一段毒鸡汤来勉励我和你

你的对手在看书

你的仇人在磨刀

你的闺蜜在减肥

隔壁的老王在练腰

而你在干嘛?


http://www.kler.cn/a/412291.html

相关文章:

  • 爬虫开发(5)如何写一个CSDN热门榜爬虫小程序
  • Python基础学习-11函数参数
  • vue3项目部署在阿里云轻量应用服务器上
  • QT6学习第四天 感受QT的文件编译
  • 文件系统的作用
  • 异步编程中,为什么必须将conn放到后台连接
  • 【大数据学习 | Spark-Core】Spark中的join原理
  • 双向链表、循环链表、栈
  • Docker desktop 改变存储位置
  • VUE练习
  • Hive的基础函数
  • 英语知识在线平台:Spring Boot技术探索
  • 流媒体拥塞控制与流控
  • 几个bev模型部署常用的命令
  • 深度学习每周学习总结J6(ResNeXt-50 算法实战与解析 - 猴痘识别)
  • Spring MVC练习(前后端分离开发实例)
  • Linux -线程互斥与同步
  • qt QDateTime详解
  • 【书生大模型实战营第四期】评测 InternLM-1.8B 实践
  • LSA详情与特殊区域
  • Pydantic 数据验证
  • 1- 9 C 语言面向对象
  • 差分 + 模拟,CF 815A - Karen and Game
  • 实现qt拖拽显示或者播放
  • linux 存储学习(nas)
  • 深入解析 MySQL 索引失效的原因与优化策略