遊戲開發中的一個小問題(設計模式)?

使用iOS SpriteKit遊戲引擎,與cocos2dx類似。工程中有一個場景類GameScene, 在場景上有兩個精靈(A,B,繼承的精靈類),現在A精靈要訪問B精靈的func方法(A,B沒有直接的關係),我在A精靈中使用的是如下方法: [((GameScene *)(self.parent)).B func]。雖然可以達到目的,直覺上這樣做是不合適的,請教這樣做會有什麼問題或者隱患。


強耦合 , 可以用事件系統來通知,解開耦合。

其實還是得具體問題具體分析,你之所以要從a中調用b,目的應該是a 觸發了一個事件,處理這個事件需要去操作一些對象,例如b,

但是直接操作b可能存在bug,b未被初始化,

如果你代碼保證b一定在a調用函數之前被初始化 ,

或者你調用b之前首先判斷一下b是否被初始化,

那麼這樣直接調用也無妨的


對Sprite的繼承在絕大多數情況下都是錯誤的設計,就連cocos2dx中很多繼承Sprite的類都會造成很大的麻煩。

首先暴露GameScene的欄位B是不好的,這樣的設計不易變更。你可以將該行代碼及其相關邏輯封裝到GameScene的成員方法中,並在A中調用之。重構之後:

[((GameScene *)(self.parent)) invokeBfunc]

然後我們再考慮重構之後的代碼是否合理,我們需要根據該段代碼的具體使用情況來討論:

  1. 如果上述代碼被Sprite開放的介面直接或間接調用,那麼違反里氏替換原則(LSP)。因為Sprite的父節點可以是任意類型的節點,而只要你寫了上述代碼則表示:A的父節點必須繼承自GameScene。這時候再次需要根據A和GameScene的關係來區別對待:
    1. 如果A的邏輯是依賴於GameScene大量的介面,並且GameScene不依賴於A。那麼你可以直接判斷self.parent是不是一個GameScene。這樣的判斷雖然很多人看著不舒服,但是並無不妥,因為邏輯就是如此。重構之後為:

      -- 我不會寫Objective-C!
      if self.parent is a instanceof GameScene then
      [((GameScene *)(self.parent)) invokeBfunc]
      else
      -- what the hell should i do?
      end

    2. 如果A的邏輯僅依賴於GameScene很少的介面,或者GameScene依賴於A。那麼應該遵守依賴倒置原則(DIP),提取一個盡量小的介面,讓GameScene實現該介面,並讓A只依賴於我們提取出來的介面
  2. 如果上述代碼不被Sprite開放的介面直接或間接調用,那麼你可以將上述代碼組成的邏輯提取到單獨的模塊,並且將Sprite*作為該類的一個欄位。經過重構後,你可能發現其類型可以是Node*,也就是說對於Node的所有子類型都可以復用該段邏輯,而不僅僅是Sprite,這是你無法從繼承中獲得的好處

如果以後再遇到類似的設計問題,請閱讀《敏捷軟體開發》中的設計原則,其可以幫你定位設計錯誤。設計模式只是那些滿足設計原則,被人們廣泛使用的東西而已。不要一有問題就去翻看設計模式的目錄,挨個對比到底應該選擇什麼模式。。。


根據題主描述:

1、A,B同屬繼承的精靈類,有相同的父類

2、A想調用B,又不想讓2者耦合(也就是直接通信),需要藉助第三者。

由此我想到的解決方法是中介者模式。

沒ROSE,簡單畫的,不知道可否?


從你的代碼: [((GameScene *)(self.parent)).B func] 可以看出 B 是 A 的 parent. 用冒泡事件傳遞的設計即可.


題主的調用方法,沒問題。

其他方法:

1.觀察者模式

2.GameScene做成單例,[GameScene sharedInstance].B

3.A含B實例(耦合度可能太高)


事件系統正解


推薦閱讀:

TAG:遊戲開發 | iOS開發 | 設計模式 | Cocos2d-x | SpriteKit |