面向对象设计原则

  • 经常出现的问题,该问题的解决方案核心
  • 软件设计过程中某一类常见问题的一般性解决方案
  • 类与相互通信的对象之间的组织关系,包括他们的角色,指责,协作方式几个方面

面向对象三大机制:

封装:隐藏内部实现 通过外部接口

继承:复用现有代码

多态:改写对象行为,不同的子类在继承父类后分别都重写覆盖了父类的方法

解构,关系之间相互独立,减少互相的影响

对象是某种拥有责任的抽象

是一系列可以被其他对象使用的公共接口

对象封装了代码和数据

对类扩展而非修改

  • 针对接口编程,只需要对象拥有需要接口

  • 继承”白箱复用“,子类父类耦合度高

对象组合”黑箱复用“,耦合度低

  • 封装变化点,实现层次间的松耦合,常变化的封装一个,不变的封装一个

  • 使用重构得到模式


单例模式

Singleton:保证一个类只有一个实例,并提供一个该实例的全局访问点,用于需要记录文件每一次修改状态的系统

​ - 创建型:负责对象创建 new对象

​ - 结构型:处理类与对象间组合

​ - 行为型:类与对象交互中的职责分配(组件间)

特点

  • 单例模式只在第一次被访问的时候创建,不会自主创建,内存的节约
  • 只存在一个对象进行运作,不用经历对象创建和销毁,节省性能
  • 可以链接游戏的各个模块,例如任何类都可以轻松调用文件系统
  • 反之,促进了项目耦合,加大维护难度,难以扩展

单线程单例模式

单例模式顾名思义就是要让该类只有一个初始化的对象,首先我们需要做的是将对象的构造方法进行私有化,这样改对象就不能在外部进行实例化new,从下图可以看到当我们进行private私有化之后,在外部已经不能访问了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 单线程唯一实例
public class Singleton
{
private static Singleton instance;
/*
* 单线程
* C# 自动生成无参构造函数,可以外界 new
* 构造函数私有,类外无法 new
* 也可以使用 Protected 使子类可以创建新实例
*/
private Singleton() { } //

public static Singleton Instance
{
get
{
if (instance == null) // 新建instance为空,类内创建唯一实例
instance = new Singleton();
return instance;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
class Test
{
public static void Main()
{
// Singleton t = new Singleton(); 构造函数私有,外界不可创建

// 通过引用唯一实例,t1 和 t2 引用同一个
Singleton t1 = Singleton.Instance;
Singleton t2 = Singleton.Instance;
}
}

多线程单例模式

为什么只能在单线程中使用?我们可以看到在每次调用Instance的时候都会进行执行if (instance == null)进行判空,但是当在多线程的时候,有可能两个线程同时满足该条件,例如:在第一个线程判断为空后,还没有进行实例化,这时候第二个线程进行了判空,将进入该语句,这样的情况就会进行两次实例化。

这是一个可以多线程使用的单例模式,进行了加锁,并进行了两次判断。private static object lockHelper = new object(); 这里可以任意写一个对象,只是用于加锁使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 多线程唯一实例
public class MutiSingleton
{
private static volatile MutiSingleton instance = null; // 加 volatile
private static object lockHelper = new object();
private MutiSingleton() { }
public static MutiSingleton Instance
{
get
{
if (lockHelper == null)
{
lock(lockHelper)
{
if (instance == null)
{
instance = new MutiSingleton();
}
}
}
return instance;
}
}
}

From:https://blog.csdn.net/Maybe_ch/article/details/102805855

  • 静态构造函数:静态字段初始化在静态字段初始化之前初始化

观察者模式

  • 为对象建立“通知依赖关系”,一个对象的状态发生改变,其他依赖对象都要知道,优化依赖关系,使其稳定

高耦合情况:不断添加对象,在父类直接修改,想到哪里写哪里,修改依赖对象影响被依赖对象,依赖了具体对象

依赖倒置,面向接口编程,为了依赖关系松耦合

  1. 依赖依赖对象的接口,多个依赖对象实现的功能类似,使用一个抽象接口

  2. 定义一个接口

  3. 两个功能(类)都使用这个接口,被依赖对象直接调用接口,变成弱依赖变成

  4. 会变化的功能抽出来作为类,减弱依赖,尽可能扩展而非修改

QQ截图20220819225809

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
// 银行账户发邮件和电话号系统-高耦合
// 银行系统 银行账户包括Email和Mobile发送功能
public class userData
{
public int id;
public string userEmail;
public string userPhone;
}

public class BankAccount
{
Emailer emailer; // 每次都直接添加功能 不稳定
Mobile mobile;
userData data;

public void WithDraw(int id)
{
emailer.SendEmail(data.userEmail);
mobile.SendNotification(data.userPhone);
}
}

// 功能都具有相同的接口
public class Emailer
{
public void SendEmail(string address)
{
//……
}
}

public class Mobile
{
public void SendNotification(string phone)
{
//……
}
}
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
// 观察者模式-低耦合
public class UserAccountArgs
{
public string ToAddress;
public string MobileNumber;
}

public class ArrayList<T>
{
void Add(T x) { }
void Remove(T x) { }
}

// 需要改变的提取成方法
public abstract class Subject // 抽象类
{
// 使用了该接口的实例
List<IAccountObserver> observerList = new List<IAccountObserver>();

// 对每一个使用接口的订阅对象执行
protected virtual void Notify(UserAccountArgs args) // 声明为虚方法可能更改
{
// 对每一个订阅对象更新
foreach (IAccountObserver observer in observerList)
observer.Updata(args);
}
// 添加列表引用
public void AddObserver(IAccountObserver observer)
{
observerList.Add(observer);
}

// 移除列表引用
public void Remove(IAccountObserver observer)
{
observerList.Remove(observer);
}
}

// 发送功能
public class BankAccount:Subject
{
// IAccountObserver emailer; // 每次都直接添加功能 不稳定 依赖接口,弱依赖
public void WithDraw(int data)
{
UserAccountArgs args = new UserAccountArgs();
Notify(args);
}
}


public interface IAccountObserver
{
void Updata(UserAccountArgs args);
}

public class Emailer: IAccountObserver
{
public void Updata(UserAccountArgs args)
{
string toAddress = args.ToAddress;
}
}

public class Mobile: IAccountObserver
{
public void Updata(UserAccountArgs args)
{
string mobileNumber = args.MobileNumber;
}
}
  • 接口:方法名 返回值 参数类型 一致

    委托:返回值 参数 一致

    C# 的 Event 中,委托充当了抽象的 Observer 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 委托
public delegate void AccountChangeEventHandle(object sender, AccountChangeEventHandleArgs args);

public class BankAccount : Subject
{
public event AccountChangeEventHandle AccountChange; // 委托类型的事件
public void WithDraw(int data)
{
UserAccountArgs args = new UserAccountArgs();
Notify(args);
}
protected virtual void OnAccountChange(AccountChangeEventHandleArgs arg)
{
if (AccountChange != null)
AccountChang(args);
}
}

From:C#设计模式合集


命令模式

  • 命令类封装了执行者方法等等各种属性,一个命令类封一种命令
  • 命令列表(接收器)存储需要执行的命令,可以add,有执行方法
  • 使用时 实例化命令类
  • 实例后add,用接收器实例执行,add一个命令就执行一次这个命令请求
  • 特点是随时发出命令,不用管谁执行,执行具体参数

命令模式:通过将命令封装成对象,实现解耦,可改变命令对象,撤销功能

readonly :只读

eg:实现按下按键物体前后左右移动时,if(按键) 执行(移动),二者耦合,使用命令模式封装移动

  • Command类
1
2
3
4
5
6
7
# Command
public abstract class Command
{
// 创建了基类要实例化,将角色移动继承自命令
public abstract void Execute(GameObject player); // abstract函数的类也是abstract
public abstract void Undo();
}
  • 将位移通过commend封装为类,这里封装了执行对象,独立命令,撤销
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# MoveCommand
public class MoveForward :Command
{
private GameObject _player;
public override void Execute(GameObject player)
{
_player = player;
_player.transform.Translate(Vector3.forward);
}
// 撤销
public override void Undo()
{
_player.transform.Translate(Vector3.back);
}
}
  • 命令管理器(列表)添加移动命令
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
public class CommandManager : MonoBehaviour
{
public static CommandManager Instance;
// 新建命令列表
private readonly List<Command> commendList = new List<Command>();

private void Awake()
{
if (Instance) Destroy(Instance);
else Instance = this;
}

// 添加命令
public void Addcommand(Command command)
{
CommandList.Add(command);
}

// 通过协程撤销
public IEnumerator Undo()
{
commendList.Reverse();
foreach (Command command in commandList)
{
yield return new WaitForSeconds(.2f);
command.Undo();
}
commandList.Clear();
}
}
  • 调用命令类实现角色移动(实例化Add命令执行)
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
public class InputHandle : MonoBehaviour
{
// 移动类实例
private readonly MoveForward moveForward = new MoveForward();
private readonly MoveLeft moveLeft = new MoveLeft();
private readonly MoveRight moveRight = new MoveRight();
private readonly MoveBack moveBack = new MoveBack();

private GameObject _player;

void Start()
{
_player = GameObject.Find("Cube");
}

void Update()
{
PlayerInputHandle();
}

private void PlayerInputHandle()
{
if (Input.GetKeyDown(KeyCode.W)
{
moveForward.Execute(_player);
CommandManager.Instance.Addcommand(moveForward);
}
// 协程撤销
if (Input.GetKeyDown(KeyCode.B)
{
StartCoroutine(CommandManager.Instance.Undo());
}
}
}

From:【游戏开发设计模式】命令模式轻松实现撤销功能,解耦以及控制对象的可参数化


对象池模式

对象池:预先定义一个包含可重用对象的池子,初始化就创建好对象并设置为非激活状态,

​ 当创建物体时激活池子里的对象:gameObject.setActive(true)

​ 当销毁物体时就将物体的状态设置为非激活:gameObject.setActive(false)

​ ObjectPool Unity自己的对象池 set clear add remove,建字典当池子也可列表

对比于创建Instantiate(GameObject) 和销毁Destroy(GameObject),性能有了极大提升

对象池(线程池)一次性创建完毕,完整的分配内存,计算量小

  • 子池子设为List,取出,放回,放回所有物体功能
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
public class Subpool
{
public List<GameObject> subPool = new List<GameObject>();
GameObject prefab;

public Subpool(GameObject pref)
{
prefab = pref;
}

// 取出
public GameObject OutPool()
{
GameObject obj = null;
foreach (GameObject _obj in subPool) // 池中找到一个隐藏的物体,取出来
{
if (_obj.activeSelf == false)
obj = _obj;
}
// 池中无物体新生成
if (obj == null)
{
obj = GameObject.Instantiate(prefab);
subPool.Add(obj); // 新生成的加到池子中
}

obj.SetActive(true); // 找到的物体显示出来
return obj;
}

// 放回池子,找到没关闭的关闭掉
public void InPool()
{
if (subPool.Count > 0)
{
foreach(GameObject _obj in subPool)
{
if (_obj.activeSelf == true)
{
_obj.SetActive(false);
break;
}
}
}
}

// 放回所有物体,开着的全关掉
public void InputAllObject()
{
foreach(GameObject _obj in subPool)
{
if (_obj.activeSelf == true)
InPool();
}
}
}

  • 父池子管理所有子池子,父池子为字典,记录自池子的名字和池子类
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
public class ObjManager
{
private Dictionary<string, Subpool> PoolDic = new Dictionary<string, Subpool>(); // 字典管理所有子池子

public GameObject OutPool(string name)
{
if (PoolDic.ContainsKey(name) == false) // 传入的自池子名字不存在,new一个新的加入字典
{
GameObject obj = Resources.Load("Prefabs/" + name) as GameObject;
Subpool _subpool = new Subpool(obj);
PoolDic.Add(name, _subpool);
}
Subpool subpool = PoolDic[name];
return subpool.OutPool();
}

public void InputPool(string objname)
{
PoolDic[objname].InPool();
}
public void InputAllObject()
{
foreach (Subpool sp in PoolDic.Values)
sp.InputAllObject();
}
}

From:Unity之设计模式:单例模式和对象池模式


From:https://shusheng007.top/2021/09/08/command-pattern/

https://github.com/shusheng007/design-patterns

工厂模式

  • 抽象产品类—-实现产品类
  • 抽象流水线类(执行方法)接口—-实现不同产品的不同执行方法,生成对应产品
  • 实现:想要哪种产品实例就实例化某种执行方法———同一类型的对象用同一种接口,每加一种产品就加一个实现接口的工厂类

抽象工厂

  • 抽象产品类—-实现产品类
  • 抽象流水线类(执行方法)接口—-实现不同产品的不同执行方法,生成对应产品,每个工厂可能生产n种产品
  • 实例化某种执行方法—产生对应产品

策略模式

  • 一个操作有好多种实现方法,而你需要根据不同的情况使用if-else等分支结构来确定使用哪种实现方式的时候
  • 算法的同一操作(参数,返回一样)
  • 算法拆开,一个算法一个类,各个封装,相互独立