1. 程式人生 > >【遊戲開發】淺談遊戲開發中常見的設計原則

【遊戲開發】淺談遊戲開發中常見的設計原則

依賴關系 unity 說過 srp des log gof https 類繼承

  俗話說得好:“設計模式,常讀常新~”。的確,每讀一遍設計模式都會有些新的體會和收獲。馬三不才,才讀了兩遍設計模式(還有一遍是在學校學的),屬於菜鳥級別的。這次準備把閱讀設計模式的想法記錄下來,並且把設計模式應用在Unity遊戲開發上,做些小案例。

什麽是設計模式

  每一種模式都在說明某種一再出現的問題,並描述解決方法的核心,之後讓你能夠舉一反三,從而解決數個類似的問題。每一種設計模式除了按照“面向對象的設計原則”加以分析設計之外,還滿足:”解決一再出現的問題“、”解決問題的方案和問題核心的關鍵點“、”可以重復利用解決方案“這樣幾個要求。

  通過引入”模式“的概念,以經驗積累的方式,將一些經常用來解決特定問題的”類設計“、”對象組裝“加以整理並定義成一種”設計模式“。而這些設計模式讓開發者在以後遇到相同的問題時,可以從中找出對應的解決方案直接使用,不必再過多地思考如何設計分析。這樣不但可以減少不必要的時間花費,還可以加強軟件的”穩定性“和”可維護性“。

遊戲開發設計中的常見7大設計原則

  雖然標題寫的是”遊戲開發設計中的常見7大設計原則“,其實這些原則本來就是適用於普遍性的面向對象設計。如果軟件設計人員能夠充分了解這些設計原則並且加以利用,那麽就可以讓實現出來的系統更加穩定、容易維護、具有更好的拓展性。

單一職責原則(SRP)

  這個原則強調的是”當設計封裝一個類時,該類應該只負責一件事“。當然,這與在類抽象化的過程中,對於該類所負責的功能有關。一個類應該只負責系統中的一個單獨功能的實現,但是對於功能的劃分和歸屬,通常也是開發過程中最困擾設計人員的問題。設計人員在一開始的時候不太容易遵循這個原則,會在項目開發的過程中,不斷地向同一個類上添加功能,最後導致類過於龐大、接口過於復雜後才會發現這個問題,最後可能讓整個項目過度依賴這個類,使得項目失去彈性。

  但是,只要通過不斷地進行”類重構“,將類中與實現相關功能的部分抽取出來,另外封裝成新的類,之後利用組合的方式,將新增的類加入到原有的類中,慢慢地就能符合類單一職責化的要求了,即項目中每一個類都只負責單一功能的實現。

開閉原則(OCP)

  一個類應該”對拓展開放,對修改封閉“。何為對拓展開放對修改封閉呢?其實這裏提到的類指的是實現系統某項功能的類。而這個功能的類,除非是修正錯誤,否則,當軟件的開發流程進入“完工測試期”或者“上市維護期”時,對於已經測試完成或者已經上線運行的功能,就應該“關閉對修改的需求”,也就是不能再修改這個類的任何接口或者實現。

  但是,當增加系統功能的需求發生的時候,又不能置之不理,所以也必須對“功能的增加保持開放”。為了滿足這個原則的要求,系統分析時就要朝向“功能接口化”的方向進行設計,將系統功能的“操作方法”向上提升,抽象化為接口,將“功能的實現”向下移到子類中。因此,在面對增加系統功能的需求時,就可以使用“增加子類”的方法來滿足。具體的實現方式是:重新實現一個新的子類,或者繼承就得實現類,並在新的子類中實現新增的系統功能。這樣,對於舊系統的功能實現就可以保持不變(封閉),同時又對功能的新增需求保持開放。

裏氏替換原則(LSP)

  這個原則指的是“子類必須能夠替換父類”。如果按照這個設計原則去實現一個有多層繼承的類群組,那麽其中的父類通常是“接口”或者“可被繼承的類”。父類中一定包含了可被子類重新實現的方法,而客戶端使用的操作接口也是由父類來定義的。客戶端在使用的過程中,必須不能使用到“對象強制轉型成子類”的語句,客戶端也不應該知道,目前正在使用的對象是哪一個子類實現的。至於使用哪個子類對象來代替父類對象,則是由類本身的對象產生機制來決定,外籍無法得知。裏氏替換原則基本上也是對於開放——封閉原則提供了一個實現的法則,說明如何設計才能保持正確的需求開放。

依賴倒置原則(DIP)

  這個原則包含了兩個主題:

  • 高層模塊不應該依賴於低層模塊,兩者都應該依賴於抽象概念;
  • 抽象接口不應該依賴於實現,而實現應該依賴於抽象接口。

  從生活中舉例來解釋第一個原則主題(高層模塊不應該依賴於低層模塊),兩者都應該依賴於抽象,可能會比單純使用軟件設計來解釋更為容易,所以下面就以汽車為例進行說明。

  汽車與汽車引擎就是一個很明顯違反這個原則的例子:汽車就是所謂的高層模塊,當要組裝一臺汽車時,需要有不同的低層模塊進行配合才能完成,如引擎、傳統系統、輪胎、汽車骨架等,有了這些低層模塊的相互配合才能完成一輛汽車的裝配。但是汽車很容易被引擎系統給限定。汽油機的汽車不能加裝柴油,柴油機的汽車不能加裝汽油。每當汽車要加油的時候,都必須根據引擎來選擇不同的加油設施,汽車因為引擎而被限定了加油的品項。雖然這是一個很難去改變的例子,但是在軟件系統的設計上,反倒有很多方法可以去避免這個“高層依賴於低層”的問題,也就是將它們之間的關系反轉,讓低層模塊按照高層模塊所定義的接口去實現。

  以電腦的組成為例,位於高層的電腦定義了USB接口,而這個接口定義了硬件所需的規格及軟件驅動程序的編寫規範。只要任何低層模塊,如儲存卡、手機、U盤、讀卡器等設備,凡是符合USB接口規範的,都能加入到電腦中,成為電腦的一部分。通過這個電腦的例子我們大概就可以明白如何由“高層模塊定義接口”再由“底層模塊遵循這個接口實現”的過程,這個過程可以讓他們之前的依賴關系反轉。同時,這個反轉的過程也說明了第二項原則的含義:“抽象接口不應該依賴於實現,而實現應該依賴於抽象接口”。當高層模塊定義了溝通接口以後,與低層模塊的溝通就應該只通過接口來進行,在具體實現上,這個接口可能是以一個類的變量或者對象引用來表示的。請註意,在使用這個變量或者對象引用的過程中,不能做任何的類型轉換,因為這樣就限定了高層模塊只能使用某一個底層模塊的特定實現。而且,子類在重新實現時,都要按照接口類所定義的方法進行實現,不應該再新增其他方法,不能讓高層模塊有利用類型轉換的方法去調用的機會。

接口隔離原則(ISP)

  “客戶端不應該被迫使用它們用不到的接口方法。”這個問題一般隨著項目開發的進行而越來越明顯。當項目中出現了一個負責主要功能的類,而且這個類還必須負責和其他子系統進行溝通時,針對每一個子系統的需求,主要類就必須增加對應的方法。但是,增加越多的方法就等同於增加類的接口的復雜度。因此每當要使用這個類的方法的時候,就要小心翼翼地從中選擇正確的方法,無形之中增加了開發和維護的難度。通過“功能的切分”和“接口的簡化”可以減少這類問題的發生,或者運用設計模式來重新規劃類,也可以減少不必要的操作接口出現在類中。

最少知識原則(LKP)

  當設計實現一個類時,這個類應該越少使用到其它類提供的功能越好。意思是,當這個類能夠只靠本身的“知識”去完成功能的話,那麽就相應地減少與其他對象“知識”的依賴度。這樣的好處就是減少了這個類與其他類的耦合度(即依賴度),換個角度來看,就是增加了這個類被不同項目復用的可能性,提高類的重用性。

少用繼承多用組合原則

  當子類繼承一個“接口類”後,新的子類就要負責重新實現接口類中所定義的方法,而且不該額外擴充接口,以符合上述多個設計原則的要求。但是,當系統想要擴充或者增加某一項功能時,讓子類繼承原來的實現類,卻也是最容易實現的方式之一。新增的子類在繼承父類後,在子類內增加想要擴充的“功能方法”並加以實現,客戶端之後就能直接利用子類對象進行新增功能的調用。

  但是對於客戶端而言,當下可能只是需要子類所提供的功能,對父類中一些額外方法並不感興趣,因為這樣會增加開發者挑選方法時的難度。例如,“鬧鐘類”可以利用繼承“時鐘類”的方式,獲得一個“時間功能”的方法,只要子類本身再另外加上“定時提醒”的功能,就能實現“鬧鐘功能”的目標。當客戶端使用“鬧鐘類”的時候,可能期待的只不過是設定鬧鐘時間的方法而已,對於取得當前時間的功能並沒有迫切的需求。因此,從“時鐘父類”繼承而來的方法,對於鬧鐘的用戶來說,可能是多余的。

  如果將設計改為在鬧鐘的類中聲明一個類型為時鐘類的“類成員”,那麽就可以減少不必要的方法出現在鬧鐘接口上,也可以減少“鬧鐘類”的客戶端對“時鐘類”的依賴。另外,在無法使用多重繼承的程序設計語言中(Java、C#等),使用組合的方式會比層層繼承的方式更加容易理解和維護,並且對於類的封裝也有比較好的表現方式。

總結

  以上只是面向對象設計中的7個基本的設計原則,卻也是最重要的原則,只有理解了這些基礎的原則之後才能繼續更深入地學習GOF所提到的23種設計模式。23種設計模式並不是教條式的規則和框架,他們都是解決問題的方法的概念的呈現。一些優秀的設計方案由於種種原因並沒有被GOF寫入到23種設計模式中,但這並不意味著它不是設計模式。GOF曾說過“沒有規定一定要與書中一模一樣的架構才能被稱為一種模式。”,比如依賴註入和控制反轉就是非常好的、應用非常廣泛的一種設計方案。

  另外,對於之後的本系列博客一些DEMO工程,將會同步到Github,點我點我。

作者:馬三小夥兒
出處:http://www.cnblogs.com/msxh/p/6921679.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

【遊戲開發】淺談遊戲開發中常見的設計原則