3.3 Immutable Object模式实战案例解析
某彩信网关系统在处理由增值业务提供商(VASP,Value-Added Service Provider)下发给手机终端用户的彩信消息时,需要根据彩信接收方号码的前缀(如1381234)选择对应的彩信中心(MMSC,Multimedia Messaging Service Center),然后转发消息给选中的彩信中心,由其负责对接电信网络并将彩信消息下发给手机终端用户。彩信中心相对于彩信网关系统而言,是一个独立的部件,二者通过网络进行交互。这个选择彩信中心的过程,我们称之为路由(Routing),而手机号前缀和彩信中心的这种对应关系,我们称之为路由表。路由表在软件运维过程中可能发生变化,例如,业务扩容带来的新增彩信中心、为某个号码前缀指定新的彩信中心等。虽然路由表在该系统中是由多线程共享的数据,但是这些数据的变化频率并不高。因此,即使是为了保证线程安全,我们也不希望对这些数据的访问进行加锁等并发访问控制,以免产生不必要的开销和问题。这时,Immutable Object模式就派上用场了。
维护路由表可以被建模为一个不可变对象,如清单3-4所示。
清单3-4 使用不可变对象维护路由表
而彩信中心的相关数据,如彩信中心设备编号、URL、支持的最大附件大小,也被建模为一个不可变对象,如清单3-5所示。
清单3-5 使用不可变对象表示彩信中心的相关数据
彩信中心信息变更的频率也同样不高。因此,当通过网络(Socket连接)通知彩信网关系统这种彩信中心信息本身或者路由表变更时,网关系统会重新生成MMSCInfo和MMSCRouter来反映这种变更,如清单3-6所示。
清单3-6 处理彩信中心信息、路由表的变更
上述代码会调用MMSCRouter的setInstance方法将MMSCRouter实例替换为新创建的实例。而新创建的MMSCRouter实例会通过其构造器生成多个新的MMSCInfo实例。
在本案例中,MMSCInfo是一个严格意义上的不可变对象。虽然MMSCRouter对外提供了setInstance方法来改变其静态字段instance的值,但它仍然可被视作一个等效的不可变对象。这是因为setInstance方法仅仅改变了instance变量指向的对象,而instance变量采用volatile关键字修饰,保证了其在多线程之间的内存可见性,所以这意味着setInstance方法对instance变量的改变无须加锁也能保证线程安全。而其他代码在通过调用MMSCRouter的相关方法获取路由信息时也无须加锁。
从图3-1的类图来看,OMCAgent类(见清单3-6)是一个Manipulator参与者实例,而MMSCInfo、MMSCRouter是一个ImmutableObject参与者实例。通过使用不可变对象,我们既可以应对路由表、彩信中心信息中不是非常频繁的变更,也可以使系统中使用路由表的代码免受并发访问控制的开销和问题。