
《游戏编程模式》-Bob Nystrom
书籍汉化版网址:https://gpp.tkchu.me/
笔记目录:https://hmxs.games/posts/11100/
原型模式

原型模式(PrototypePattern)是一种创建型设计模式,设计之初是为了解决"重复创建类似对象"这一问题;原型模式最重要的特性便是“clone”,其提倡通过预先创建一个"原型对象",再通过对原型进行克隆的方式创建对象。
在OOP中,类拥有方法,而实例拥有属性,但实际上这不是OOP的唯一实现,一种名为Self的语言使用了原型作为构建OOP体系的核心,在Self中不再区分方法与属性,对象拥有一切,而要建立新的对象便需要克隆一个原有对象并向其中添加东西,这种构想听上去非常理想,但是在实际编码时却并不方便。而现代的JavaScript则吸收了部分Self语言的思想,但JS为其添加上了类的外衣,使其更加易用。
虽然在今时今日原始的原型模式实现方式已经有些过时,很多语言已经将原型模式纳入了其语言特性中(如Unity中的Instantiate方法),但其思想仍然有值得学习借鉴的地方,如在我们通过JSON配置对象数据时,我们便可以将一部分相同的数据作为原型部分来提高效率。
单例模式
基本定义与概念
单例模式(Singleton)是最易于使用,同时也存在着许多争议的一种设计模式。其理念是:**保证一个类只有一个实例,并且提供访问该实例的全局访问点。**从另一个角度来说,单例模式是一种将全局状态封装为类的模式。其有着鲜明的优缺点,它是一种方便好用的设计模式,但是它也有着很多缺陷,依据本书作者的话来说,使用它更接近一种“饮鸩止渴”的状态。
优点
- 使用懒汉式单例时,惰性加载可以一定程度上节省内存,没人调用它时,其便不会被创建
- 与静态/全局状态相比,其在运行时进行实例化,这让其初始化变得方便,不必担心引用问题
- 因其可以被全局访问,调用它非常轻松惬意
缺点
- 全局访问的特性,让理解代码变得困难,同时并不安全,因为所有人都能访问到,也让并行开发变得困难
- 使用懒汉式单例时,惰性加载让准确控制其加载时机变得困难,无法手动控制其内存
- 饿汉式单例随没有惰性加载的问题,但其相比静态类很难突出其优势
Unity简单实现
Singleton泛型基类的基本实现:分为不继承Mono与继承Mono两个版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| namespace SingletonPattern { public abstract class Singleton<T> where T : Singleton<T>, new() { private static readonly Lazy<T> InstanceHolder = new(() => { var instance = new T(); instance.OnInstanceInit(instance); return instance; });
public static T Instance => InstanceHolder.Value;
protected virtual void OnInstanceInit(T instance) {}
protected Singleton() { } } public abstract class SingletonMono<T> : MonoBehaviour where T : SingletonMono<T> { private static T _instance; private static readonly object LockObj = new(); private static bool _isApplicationQuitting;
public static T Instance { get { if (_isApplicationQuitting) { Debug.LogWarning($"[{typeof(T)}] Instance is destroyed. Returning null."); return null; }
lock (LockObj) { if (_instance) return _instance; _instance = FindObjectOfType<T>(); if (_instance) return _instance; var singletonObject = new GameObject($"{typeof(T).Name}_SingletonMono"); _instance = singletonObject.AddComponent<T>(); return _instance; } } }
protected virtual bool KeepAliveAcrossScenes => true;
protected virtual void Awake() { lock (LockObj) { if (!_instance) { _instance = this as T; if (KeepAliveAcrossScenes) DontDestroyOnLoad(gameObject); } else if (_instance != this) { Debug.LogWarning($"[{typeof(T)}] Duplicate instance detected. Destroying {name}."); DestroyImmediate(gameObject); } } }
protected virtual void OnDestroy() { lock (LockObj) if (_instance == this) _instance = null; }
protected virtual void OnApplicationQuit() => _isApplicationQuitting = true; } }
|
到底用不用单例?
- 很多单例可以被替换为静态类
- 全局访问不是一个好性质,我们应该尽量缩小可访问单例的范围
- 有时我们实际上只需要“全局唯一实例”的性质,那么用一个静态布尔值标识一下就行了