Unity中常用的几种设计模式
发表于2018-06-11
设计模式有23种之多,但是我们经常使用的也就是那么几种,下面就和大家介绍常用的这几种设计模式:“单例模式”,“观察者模式”,“迭代器模式”,“访问者模式”。
一、单例模式
概念很简单,保证一个类仅有一个实例,并提供一个访问它的全局访问点。我就提供两段代码就好了,在游戏当中,有两种类,一种是不继承MonoBehavior,另外一种是继承它的,首先看不继承的,
Class Singleton { Static MySingleton; // 单件对象,全局唯一的。 Static Instance() { if(MySingleton == null) MySingleton = new MySingleton(); return MySingleton; } // 对外暴露接口 }
下面来看继承自MonoBehavior的类,
Class Singleton:MonoBehavior { Static MySingleton; Static Instance() { return MySingleton; } void Awake() { MySingleton = this; } }
直接在游戏开发中这么使用就可以了。
二、观察者模式
概念:它将对象与对象之间创建一种依赖关系,当其中一个对象发生变化时,它会将这个变化通知给与其创建关系的对象中,实现自动化的通知更新。
在游戏开发中,比如UI上有一个下拉的List,我选择了其中的每一项,都会导致UI界面的变化,比如我选择“强化”,对应出现强化装备的界面;我选择“镶嵌”,就会出现镶嵌的界面。
伪代码如下:
Class Subject { // 对本目标绑定一个观察者 Attach( Observer ); // 解除一个观察者的绑定 DeleteAttach( Observer ); // 本目标发生改变了,通知所有的观察者,但没有传递改动了什么 Notity() { For ( …遍历整个ObserverList …) { pObserver ->Update(); } } // 对观察者暴露的接口,让观察者可获得本类有什么变动 GetState(); } // 观察者/监听者类 Class Observer { // 暴露给对象目标类的函数,当监听的对象发生了变动,则它会调用本函数通知观察者 Void Update () { pSubject ->GetState(); // 获取监听对象发生了什么变化 TODO:DisposeFun(); // 根据状态不同,给予不同的处理 } }
这个很好理解了吧!
三、迭代器模式
我们就拿C#中的迭代器为例直接说明即可,既能了解了迭代器模式的概念,有了解了C#中迭代器是如何实现的。
迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。
在。NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。
在C#1中已经内建了对迭代器的支持,那就是foreach语句。使得能够进行比for循环语句更直接和简单的对集合的迭代,编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代完成之后会释放迭代器。但是在C#1中,实现一个迭代器是相对来说有点繁琐的操作。C#2使得这一工作变得大为简单,节省了实现迭代器的不少工作。
public System.Collections.IEnumerator GetEnumerator() { for (int i = 0; i < 10; i++) { yield return i; } } static void Main() { ListClass listClass1 = new ListClass(); foreach (int i in listClass1) { System.Console.Write(i + " "); } // Output: 0 1 2 3 4 5 6 7 8 9} }
四、访问者模式
当我们希望对一个结构对象添加一个功能时,我们能够在不影响结构的前提下,定义一个新的对其元素的操作。
例如场景管理器中管理的场景节点,是非常繁多的,而且种类不一,例如有Ogre中的Root, Irrchit中就把摄象机,灯光,Mesh,公告版,声音都做为一种场景节点,每个节点类型是不同的,虽然大家都有共通的Paint(),Hide()等方法,但方法的实现形式是不同的,当我们外界调用时需要统一接口,那么我们很可能需要需要这样的代码
Hide( Object ) { if (Object == Mesh) HideMesh(); if (Object == Light) HideLight(); … }
此时若我们需要增加一个Object新的类型对象,我们就不得不对该函数进行修正。而我们可以这样做,让Mesh,Light他们都继承于Object,他们都实现一个函数Hide(),那么就变成
Mesh::Hide( Visitor ) { Visitor.Hide (Mesh); }
Light::Hide(Visitor ){ Visitor.Hide (Light); }
意思就是说,Mesh的隐藏可能涉及到3个步骤,Light的隐藏可能涉及到10个步骤,这样就可以在每个自己的Visitor中去实现每个元素的隐藏功能,这样就把很多跟元素类本身没用的代码转移到了Visitor中去了。
每个元素类可以对应于一个或者多个Visitor类。比如我们去银行柜台办业务,一般情况下会开几个个人业务柜台的,你去其中任何一个柜台办理都是可以的。我们的访问者模式可以很好付诸在这个场景中:对于银行柜台来说,他们是不用变化的,就是说今天和明天提供个人业务的柜台是不需要有变化的。而我们作为访问者,今天来银行可能是取消费流水,明天来银行可能是去办理手机银行业务,这些是我们访问者的操作,一直是在变化的。
伪代码如下:
// 访问者基类 Class Visitor { Virtual VisitElement( A ){ … }; // 访问的每个对象都要写这样一个方法 Virtual VisitElement( B ){ … }; } // 访问者实例A Class VisitorA { VisitElement( A ){ … }; // 实际的处理函数 VisitElement( B ){ … }; // 实际的处理函数 } // 访问者实例B Class VisitorB { VisitElement( A ){ … }; // 实际的处理函数 VisitElement( B ){ … }; // 实际的处理函数 } // 被访问者基类 Class Element { Virtual Accept( Visitor ); // 接受访问者 } // 被访问者实例A Class ElementA { Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函数 } // 被访问者实例B Class ElementB { Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函数 }