說明:本篇闡述的問題,是基於前面的遊戲服務器架構設計的。算法
衆所周知,Spring最擅長的領域是無狀態服務的構建,而遊戲(尤爲是玩法部分)是有狀態的。以棋牌遊戲爲例,玩法服務裏面大概涉及如下兩類對象:
一、無狀態的服務,好比數據讀寫、通訊等;
二、與遊戲桌子綁定的有狀態類,好比桌子自己,狀態機,玩家的遊戲狀態等。
後者確定是要訪問前者提供的方法的,那麼後者怎麼拿到前者的引用呢。服務器
咱們一開始的作法是,無狀態的服務作成Spring Bean,而後在啓動的時候,把這些service的引用放到一個靜態了類的字段裏面,相似這樣:架構
public class ServiceContainer { public static ServiceBean1 bean1; public static ServiceBean2 bean2; ... public static init(ApplicationContext context) { bean1 = context.getBean(ServiceBean1.class); bean2 = context.getBean(ServiceBean2.class); ... } }
而後有狀態的對象就能夠簡單地經過ServiceContainer.bean1來訪問無狀態的服務了。
不過代碼多了之後,發現這種方式實在有點糟糕。 因爲對ServiceContainer的訪問實在太簡單了,致使你們隨意地在裏面添加新的字段,最後對象之間的耦合依賴很是混亂,徹底沒有享受到使用Spring的好處。優化
爲了解決問題,首先確立一個原則,不容許經過靜態方法、靜態字段來暴露服務;單例模式固然也不容許(本質仍是靜態方法),既然用了Spring,就要按Spring的架構哲學來搞。
第一個辦法是這樣的,在建立一個有狀態的對象的時候,若是這個對象須要依賴某些Service,那麼在初始化的過程當中手動注入進去,以遊戲桌table爲例,代碼相似這樣:架構設計
public class GameTable { public ServiceBean1 bean1; public ServiceBean2 bean2; ... public init(ApplicationContext context) { bean1 = context.getBean(ServiceBean1.class); bean2 = context.getBean(ServiceBean2.class); ... } }
這個方法,雖然看起來和ServiceContainer半斤八兩,但實際上要好不少,至少某個玩法的table依賴哪些服務一目瞭然。不過產品功能複雜性增長之後,table依賴的服務愈來愈多,仍是會讓人很不爽。設計
table裏面注入了太多的service,本質上說明遊戲玩法邏輯的拆分不夠細。
在前邊的文章裏面,咱們的核心玩法邏輯拆分紅table(桌子),state(狀態機),messageProcessor(消息處理器),如今從新捋一下這些角色的職責:日誌
一、table以及相關的類是遊戲玩法的領域模型,專一於遊戲的數據狀態和基本規則算法;
二、state狀態機用來簡化流程控制,專一於消息的過濾,和狀態的切換;
三、messageProcessor,專一於消息的處理,服務於玩法的全部桌子,也就是說它是無狀態的;
四、其餘業務邏輯,好比數據的存取,遊戲日誌處理,外圍功能(好比任務)。code
其中table和state都是內存對象,而messageProcessor和其餘邏輯都是標準的Service Bean,state將消息過濾後,委託給messageProcessor來處理,依據處理結果來遷移狀態。
一個典型的messageProcessor接口可能相似這樣的:對象
public boolean processPlayerJoin(Table table, Player player, Message message) { if (玩家是否知足入桌條件) { table.addPlayer(player); return true; } return false }