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)泛型单例模式
cs 复制代码
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 关键字来实例化该类。

cs 复制代码
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 来访问它。

cs 复制代码
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 的唯一实例。

单例使用方式
cs 复制代码
GameManager.instance.Walk();

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

两种方法比较

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

(1)使用静态方法:

优点:

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

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

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

缺点:

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

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

优点:

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

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

缺点:

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

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

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

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

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

今天是2024年11月23日

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

你的对手在看书

你的仇人在磨刀

你的闺蜜在减肥

隔壁的老王在练腰

而你在干嘛?

相关推荐
张小姐的猫8 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充11 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充11 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充11 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充11 小时前
2.面向对象的特性
设计模式
杨充11 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充11 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充11 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯14 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法