面向对象设计原则--》提高程序的复用性、扩展性、维护性、移植性
一、创建型--关注对象创建的问题
1、单例Singleton 关键词:唯一
一个类只创建一个对象,且该对象全局共享
using UnityEngine;
public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
private static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = FindAnyObjectByType<T>();
if (instance == null)
{
new GameObject("Singleton of" + typeof(T)).AddComponent<T>();
}
else
{
instance.Init();
}
}
return instance;
}
}
protected void Awake()
{
if (instance == null)
{
instance = this as T;
Init();
}
}
public virtual void Init()
{ }
}
在非unity环境下创建单例
class example
{
//不允许在类外创建对象
private example(){}
//饿汉模式
//public static example instance{get; private set;}
//static example()
//{
//instance = new example();
缺点:过早占用内存
//}
懒汉模式
//private static example instance;
按需加载
//public static example Instance
//{
//get
//{
缺点:有可能多线程同时访问,创建多个对象
//if(instance == null)
//{
//instance = new example();
//}
//return instance;
//}
//}
private static example instance;
private object locker = new object();
//按需加载
public static example Instance
{
get
{
//双重检查:
//第一层:避免每次访问instance属性都lock
//第二层:排队创建对象时,只有第一个会创建
if(instance == null)
{
lock(locker)
{
if(instance == null)
{
instance = new example();
}
}
}
return instance;
}
}
}
2、简单工厂 关键词:从多个对象中选一个对象
作用:将对象的创建和表示(使用)分离
eg:技能系统创建技能效果和选区类
public static class DeployerConfigFactory
{
private static Dictionary<string, object> cache;
static DeployerConfigFactory()
{
cache = new Dictionary<string, object>();
}
public static IAttackSelector CreateIAttackSelector(SkillStatus skillStatus)
{
//skillStatus.selectorType
//选区对象命名规则
//命名空间.+枚举名+AttackSelector 为了创建一个对象
string className = string.Format("skill.{0}AttackSelector", skillStatus.selectorType);
return CreateObject<IAttackSelector>(className);
}
public static IImpactEffect[] CreateIImpactEffect(SkillStatus skillStatus)
{
IImpactEffect[] impactEffects = new IImpactEffect[skillStatus.impactType.Length];
//影响效果命名规范:
//命名空间.+impactType[?]+Impact
for (int i = 0; i < skillStatus.impactType.Length; i++)
{
string impactName = string.Format("skill.{0}Impact", skillStatus.impactType[i]);
impactEffects[i] = CreateObject<IImpactEffect>(impactName);
}
return impactEffects;
}
private static T CreateObject<T>(string className) where T : class
{
if (!cache.ContainsKey(className))
{
Type type = Type.GetType(className);
object instance =Activator.CreateInstance(type);
cache.Add(className, instance);
}
return cache[className] as T;
}
}
3、抽象工厂 关键词:多个系列选一个系列
提供一个系列相关或相互依赖对象的接口(抽象),而无需指向他们的具体对象。
依赖倒置思想
4、对象池 关键词:缓存
问题:频繁创建对象,消耗CPU的性能
eg:利用unity对物体的启用和弃用,实现对象池的创建
public interface IResetable
{
void OnReset();
}
public class GameObjectPool : MonoSingleton<GameObjectPool>
{
private Dictionary<string, List<GameObject>> cahe;
public override void Init()
{
base.Init();
cahe = new Dictionary<string, List<GameObject>>();
}
public GameObject CreateObject(string key , GameObject prefab , Vector3 pos, Quaternion quaternion)
{
GameObject go = FindUsableObject(key);
if (go == null)
{
go = AddObject(key, prefab);
}
UseObject(pos, quaternion, go);
return go;
}
/// <summary>
///
/// </summary>
/// <param name="go">回收对象</param>
/// <param name="delay">延迟</param>
public void CollectObject(GameObject go,float delay =0)
{
StartCoroutine(CollectObjectDelay(go,delay));
}
//清空
//一般清空是倒着清空
public void Clear(string key)
{
if (cahe.ContainsKey(key))
{
foreach(GameObject go in cahe[key])
{
Destroy(go);
}
cahe.Remove(key);
}
}
public void ClearAll()
{
foreach (List<GameObject> go in cahe.Values)
{
foreach(GameObject go1 in go)
{
Destroy(go1);
}
}
cahe.Clear();
}
private IEnumerator CollectObjectDelay(GameObject go,float delay)
{
yield return new WaitForSeconds(delay);
go.SetActive(false);
}
private static void UseObject(Vector3 pos, Quaternion quaternion, GameObject go)
{
go.transform.position = pos;
go.transform.rotation = quaternion;
go.SetActive(true);
//遍历执行物体中需要重置的脚本
foreach(var item in go.GetComponents<IResetable>())
{
item.OnReset();
}
}
private GameObject AddObject(string key, GameObject prefab)
{
GameObject go = Instantiate(prefab);
if (!cahe.ContainsKey(key))
cahe.Add(key, new List<GameObject>());
cahe[key].Add(go);
return go;
}
private GameObject FindUsableObject(string key)
{
if (cahe.ContainsKey(key))
return cahe[key].Find(g => !g.activeInHierarchy);
return null;
}
}
二、结构型--关注类与对象的结构问题,组织类与类的关系
1、外观 关键词:向导
问题:不应该让客户代码来组织访问流程
封装各类函数,提供给客户一个简单的访问接口(方法)
eg:技能系统类
///<summary>
///提供简单的技能释放
///<summary>
///
[RequireComponent(typeof(CharacterSkillManage))]
public class CharacterSkillSystem : MonoBehaviour
{
private CharacterSkillManage skillManager;
private SkillStatus skill;
void Start ()
{
skillManager = GetComponent<CharacterSkillManage>();
}
public void AttackUseSkill(int id,bool isBatter = false)
{
if(skill !=null&&isBatter)
{
id = skill.nextBatterId;
}
skill=skillManager.PrepareSkill(id);
if (skill == null)
{
Debug.LogError("技能释放失败");
return;
}
skillManager.GenerateSkill(skill);
}
public void UseRandomSkill()
{
var useableSkills =skillManager.skillStatus.FindAll(b => skillManager.PrepareSkill(b.skillID) != null);
if (useableSkills.Length==0)
{
return;
}
int index = Random.Range(0,useableSkills.Length);
AttackUseSkill(useableSkills[index].skillID);
}
}
2、适配器 关键词:转接
问题:两个功能一致,接口不一样而不能直接复用
eg:ResourceManager
//通过名字找资源
public class ResourceManager
{
private static Dictionary<string,string> configMap;
//初始化类的静态数据成员
//类被加载时只执行一次
static ResourceManager()
{
string fileContent = GetConfigFile("ConfigMap.txt");
BuildMap(fileContent);
}
private static void BuildMap(string fileContent)
{
configMap = new Dictionary<string, string>();
using (StringReader stringReader = new StringReader(fileContent))
{
//当程序退出using代码块,将自动释放内存
string line;
while ((line = stringReader.ReadLine()) != null)
{
string[] keyValue = line.Split('=');
configMap.Add(keyValue[0], keyValue[1]);
}
}
}
public static string GetConfigFile(string fileName)
{
//ConfigMap.txt
// var url = new System.Uri(Path.Combine(Application.streamingAssetsPath, "ConfigMap.txt"));
// UnityWebRequest request = UnityWebRequest.Get(url);
// request.SendWebRequest();
var url = new System.Uri(Path.Combine(Application.streamingAssetsPath, fileName));
UnityWebRequest request = UnityWebRequest.Get(url);
request.SendWebRequest();
if (request.error == null )
{
while (true)
{
if (request.downloadHandler.isDone)
{
return request.downloadHandler.text;
}
}
}
else
{
Debug.LogError("读取ConfigMap文件失败");
return string.Empty;
}
}
public static T Load<T>(string name)where T : Object
{
string path = configMap[name];
return Resources.Load<T>(path);
}
}
3、桥接 关键词:链接两端的变化
问题:组合时产生了排列组合的关系
将抽象部分和实现部分分离,使他们独立变化
eg:技能系统--技能释放器,选区和技能效果分别与释放器为关联关系
public abstract class SkillDeployer : MonoBehaviour
{
private SkillStatus skillStatus;
public SkillStatus SkillStatus
{
get
{
return skillStatus;
}
set
{
skillStatus = value;
InitDeployer();
}
}
private IAttackSelector selector;
private IImpactEffect[] impactArray;
private void InitDeployer()
{
selector = DeployerConfigFactory.CreateIAttackSelector(skillStatus);
impactArray = DeployerConfigFactory.CreateIImpactEffect(skillStatus);
}
public void CalculateTargets()
{
skillStatus.attackTargets = selector.SelectTarget(skillStatus, transform);
}
public void ImpactTargets()
{
for (int i = 0; i < impactArray.Length; i++)
{
impactArray[i].Execute(this);
}
}
public abstract void DeployerSkill();
}
三、行为型--关注对象交互问题
1、策略 关键词:算法切换
多态,调父执行子,依赖倒置
把算法一个一个封装起来,利用父物体调用
2、观察者 关键词:通知
事件,一对多的依赖关系
观察者:被通知的对象就是观察者
eg:辅助线程将unity的API通过方法委托交给主线程做的方法
这里被观察的对象被忽略,实际上是Update为被观察到后调用的方法
可以参考接收到消息后,调用接收到消息后的事件
public class CrossAssistance : MonoSingleton<CrossAssistance>//自己写的单例方法,需要挂,不要让他自己创建
{
//用一个类封装数据
class DelayedItem
{
public Action CurrentAction { get; set; }
public DateTime Time { get; set; }
}
//所有要执行的方法委托
private List<DelayedItem> actionList;
//对外提供一个执行的方法
public void ExecuteOnMainThread(Action actionPre,float delay =0)
{
lock (actionList)//为了防止删除和增加同时进行
{
//添加方法委托
var item = new DelayedItem()
{
CurrentAction = actionPre,
//为了防止调用unity的API,利用c#的时间
Time = DateTime.Now.AddSeconds(delay)
};
actionList.Add(item);
}
}
//初始化
public override void Init()
{
base.Init();
actionList = new List<DelayedItem>();
}
private void Update()
{
//执行,从后往前移除元素更方便
for (int i = actionList.Count - 1; i >= 0; i--)
{
if(DateTime.Now > actionList[i].Time)
{
actionList[i].CurrentAction();
actionList.RemoveAt(i);
}
}
}
}
3、中介者 关键词:群聊
多对多的依赖关系,C/S架构,通过服务器通知多个客户端,实现多个客户端互相通信
eg:网络服务器框架
public class ServerUdpNetworkService : MonoSingleton<ServerUdpNetworkService>
{
private UdpClient udpService;
private Thread threadReceive;
//创建Socket对象(由登录窗口传递服务端Ip,端口)
public event EventHandler<MessageArriveEventArgs> MessageArrive;
private List<IPEndPoint> allClientEP;
public override void Init()
{
base.Init();
allClientEP = new List<IPEndPoint>();
}
public void Initialized(string severIP, int serverPort)
{
//跨场景不销毁当前游戏对象
DontDestroyOnLoad(this.gameObject);
//创建服务端终结点
IPEndPoint serverEP = new IPEndPoint(IPAddress.Parse(severIP), serverPort);
udpService = new UdpClient(serverEP);//随机分配可用的端口
threadReceive = new Thread(ReceiveChatMessage);
threadReceive.Start();
}
//发送数据
public void SendChatMessage(ChatMessage msg, IPEndPoint remote)
{
byte[] buffer = msg.ObjectToBytes();
udpService.Send(buffer, buffer.Length, remote);
}
//接收数据
private void ReceiveChatMessage()
{
while (true)
{
IPEndPoint remote = new IPEndPoint(IPAddress.Any, 0);
byte[] data = udpService.Receive(ref remote);
ChatMessage msg = ChatMessage.BytesToObject(data);
//根据消息类型,执行相关逻辑
OnMessageArrived(msg,remote);
//引发事件
if (MessageArrive == null) continue;
MessageArriveEventArgs args = new MessageArriveEventArgs
{
ArriveTime = DateTime.Now,
Message = msg
};
//在主线程中引用委托方法
CrossAssistance.Instance.ExecuteOnMainThread(() => { MessageArrive(this, args); });
}
}
//引发数据(1、事件参数类2、委托3、声明事件4、引发)
private void OnApplicationQuit()
{
threadReceive.Abort();
udpService.Close();
}
private void OnMessageArrived(ChatMessage msg,IPEndPoint remote)
{
switch (msg.Type)
{
case MessageType.Offline:
//删除客户端
allClientEP.Remove(remote);
break;
case MessageType.Online:
//添加客户端
allClientEP.Add(remote);
break;
case MessageType.General:
//转发(需要客户端的终结点)
//foreach (var item in allClientEP)
// SendChatMessage(msg,item);
allClientEP.ForEach(item => SendChatMessage(msg, item));
break;
}
}
}
4、迭代器 关键词:遍历
foreach的迭代原理/协程
一个聚合对象,提供一个让别人轻松访问它的元素,而不暴露其内部结构
5、状态 关键词:状态决定行为
状态的改变,从而带来行为上的变化
eg:状态机
详情请见状态机部分