Bootstrap

阶段总结--设计模式(以unity为例)

面向对象设计原则--》提高程序的复用性、扩展性、维护性、移植性

一、创建型--关注对象创建的问题

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:状态机

详情请见状态机部分

;