前言
七大原则:
单一职责原则(Single Responsibility Principle);
开闭原则(Open Closed Principle);
里氏替换原则(Liskov Substitution Principle);
迪米特法则(Law of Demeter);
接口隔离原则(Interface Segregation Principle);
依赖倒置原则(Dependence Inversion Principle);
合成复用原则(Composite Reuse Principle);
只有一个目的:
让程序员可以更好的偷懒,让所写过的代码可以应对各种各样的需求。
定义:
单例模式是一种创建型 设计模式,它确保一个类只有一个实例 ,并提供了一个全局访问点来访问该实例。
应用场景:
全局只想要一个实例对象,避免多个实例对象会存在数据不统一等情况下使用,具体场景如下:
-
全局游戏管理-GameManager:管理游戏运行时的状态
-
全局音频管理-AudioManager:管理游戏运行时的背景音乐、音效、人声等
-
全局数据代理-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() {}
}
具体案例:
- 简易计数器
场景布局如下:
具体代码如下:
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() {
}
}
优点(不考虑线程安全):无
缺点:
- 程序启动时就直接全部初始化完成,占用大量内存,有可能有些单例对象从头到尾都没使用过,却一直占据程序内存。
懒汉式-延迟初始化
以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() {}
}
优点:
- 调用才会初始化,不会一开始就占据程序内存。
缺点(不考虑线程安全):无
总结:
优点:
- 在内存中只有一个实例对象,不会过多创建对象,保证数据统一性。
- 避免对资源的多重占用。
缺点:
- 由于不能多态(进行继承),导致可拓展性差,所有功能都只能写在一个类统一管理。
题外话:
作者打算实现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的写法非常的冗余,没有人会选择此写法。