说明:本篇阐述的问题,是基于前面的游戏服务器架构设计的。

问题

众所周知,Spring最擅长的领域是无状态服务的构建,而游戏(尤其是玩法部分)是有状态的。以棋牌游戏为例,玩法服务里面大概涉及以下两类对象:
1、无状态的服务,比如数据读写、通信等;
2、与游戏桌子绑定的有状态类,比如桌子本身,状态机,玩家的游戏状态等。
后者肯定是要访问前者提供的方法的,那么后者怎么拿到前者的引用呢。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

我们一开始的做法是,无状态的服务做成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(消息处理器)。 首先消息处理器可以做成一个service,然后仔细查看table和state里面的逻辑,尝试把这些逻辑拆分出来,形成一个或多个service,这些service可以同时服务于多个table。

这样,最终回归了我们设计的初衷,table专注于的处理内存中游戏状态;玩法运行过程中所需消息处理、数据存取、配置更新等,包装成若干个Service(一般不会太多,2~3个);table只会直接依赖这几个Service。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄