1. 程式人生 > >Unity中常用的幾種設計模式

Unity中常用的幾種設計模式

23種設計模式,實在是太多了,而且其中有一些看著還貌似差不多,讓人很費解,好不容易理解了每一種設計模式的含義,並且看了一堆虛擬碼之後,高高興興的合上了書本去玩幾把LOL,贏了幾把之後腦袋裡關於那23種設計模式的概念就剩下80%了,然後接下來的每日工作中,基本寫程式碼的時候也用不到啊!老闆催著讓你做功能,你就還哪裡記得去使用設計模式啊,就開始亂寫吧,日復一日,23種設計模式基本就和你拜拜了,再見了。

其實呢,在遊戲開發中,我們能夠在Unity中看見的,和我們經常使用的也就是那麼幾種,在其他軟體設計中,同樣也就是經常用這麼幾種,那些“備忘錄”模式,“責任鏈”模式等等,基本上不用,下面我們就說說我們常用的這幾種吧:“單例模式”,“觀察者模式”,“迭代器模式”,“訪問者模式”。(順便插一句,那些各種工廠模式,我就不多說了,都是很容易理解的。)

1. 單例模式

概念很簡單,保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。我就提供兩段程式碼就好了,在遊戲當中,有兩種類,一種是不繼承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;

}

}

直接在遊戲開發中這麼使用就可以了。

2. 觀察者模式

概念:它將物件與物件之間建立一種依賴關係,當其中一個物件發生變化時,它會將這個變化通知給與其建立關係的物件中,實現自動化的通知更新。

在遊戲開發中,比如UI上有一個下拉的List,我選擇了其中的每一項,都會導致UI介面的變化,比如我選擇“強化”,對應出現強化裝備的介面;我選擇“鑲嵌”,就會出現鑲嵌的介面。

虛擬碼如下:

Class Subject
{
      // 對本目標繫結一個觀察者 Attach( Observer );
      // 解除一個觀察者的繫結   DeleteAttach( Observer );
      // 本目標發生改變了,通知所有的觀察者,但沒有傳遞改動了什麼
      Notity()
      {
             For ( …遍歷整個ObserverList …)
             { pObserver ->Update(); }
      }
      // 對觀察者暴露的介面,讓觀察者可獲得本類有什麼變動       GetState();
}

// 觀察者/監聽者類
Class Observer
{
      // 暴露給物件目標類的函式,當監聽的物件發生了變動,則它會呼叫本函式通知觀察者
      Void Update ()
      {
            pSubject ->GetState();  

// 獲取監聽物件發生了什麼變化
            TODO:DisposeFun();  

// 根據狀態不同,給予不同的處理
      }
}

這個很好理解了吧!

3.  迭代器模式

我們就拿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}

}

4. 訪問者模式

當我們希望對一個結構物件新增一個功能時,我們能夠在不影響結構的前提下,定義一個新的對其元素的操作。

例如場景管理器中管理的場景節點,是非常繁多的,而且種類不一,例如有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); };   

 // 呼叫註冊到訪問者中的處理函式