头图

单例模式(Singleton)是设计模式里实现起来最简单,使用最普遍的一种。

单例模式确保了一个类在任意时刻只有一个实例存在,同时提供了一个全局访问点来获取这个实例。在单例模式的使用场合下,确保某个类在整个系统中只有一个实例非常重要,比如配置管理器、线程池、缓存或者日志对象等。使用单例模式可以避免由于多个实例造成的资源使用冲突,或是数据不一致的问题。

单例模式按照唯一的实例创建时间点的不同,可以分为 LazyEager 两种变体,有的文档里又称为懒汉式单例模式饿汉式单例模式

使用 ABAP 编程时,当对象实例在全局访问点里被请求时,才创建实例的实现方式,称为 Lazy 单例模式。
在 CLASS_CONSTRUCTOR 里创建实例的实现方式,称为 Eager 单例模式。

在 ABAP 里实现 Eager 型单例模式比较简单,新建一个类,将 Instance Generation 设置成 Private

然后定义一个 Static 属性 so_instance, 用来维护该类唯一的一个实例:

将类的构造函数设置成 Private:

在该构造函数里,随便打印一些内容:

method CONSTRUCTOR.
    WRITE:/ 'do some initialization work'.
endmethod.

在 CLASS_CONSTRUCTOR 里完成唯一实例的初始化工作。

method CLASS_CONSTRUCTOR.
    CREATE OBJECT SO_INSTANCE.
  endmethod.

然后在全局访问点,GET_INSTANCE 方法里,返回之前在 CLASS_CONSTRUCTOR 里创建好的唯一实例。

该单例的消费者,在任意时间点,都只能通过 GET_INSTANCE 方法,获得唯一的对象实例。在该类的实现体外,企图使用 CREATE OBJECT 额外创建一个新的对象实例,会遇到语法错误:

An instance of the class cannot be created outside the class.

到此为止,都是大家非常熟悉的内容。

有没有一种办法,能够在 ZCL_PRIVATE_TEST 类的外部,再得到另一个不同的对象实例呢?如果可以的话,从某种程度上说,ABAP 的单例模式,就已经被破坏了。

技术上来说,确实有一种办法,即先把 GET_INSTANCE 返回的实例序列化成 XML 字符串,然后再把这个 XML 字符串,反序列化成对象实例。反序列化后得到的新对象实例,和原始的实例相比,已经是两个不同的对象实例了

当然为了让一个类能够被序列化,在 ABAP 里,需要给它添加一个接口 IF_SERIALIZABLE_OBJECT. 这个接口不包含任何方法,也称为 Tag Interface,唯一的作用,就是告诉 ABAP 框架,这个类需要支持 ABAP 的序列化和反序列化。

ABAP 无法像 Java 和 TypeScript 这些编程语言一样,从语言层面支持注解(Annotations)特性,所以只有用 Tag Interface 来实现同样的目的,即给类提供额外的标注信息,这些标准信息仅供框架解析和使用,不会对应用程序的执行造成任何影响。

看下面这段代码。第三行到第五行,声明了类的三个实例,其中实例 1 和实例 2,都是通过 GET_INSTANCE 返回的,因此这两个实例完全相同,所以第 13 行的打印语句,lo_instance1 = lo_instance2 表达式的布尔值计算结果,为 abap_true.

并且我们注意到类的 CONSTRUCTOR 构造函数里的打印语句,只执行了一次,说明在整个过程中,类的实例化过程,只发生了一次。

再继续往下看代码,第 17 行,实例1 被序列化成 XML 字符串,存储在变量 s 里。
代码第 19 行,XML 字符串 s 又被反序列化,还原成了实例 3.

此时实例 3 和实例 1 已经是完全不同的两个实例了,所以 lo_instance1 = lo_instance3 的表达式布尔值计算结果,为 abap_false.

这个行为也能在下图的 ABAP 调试器里清楚地观察到:在 ABAP 调试器里,实例1 和实例 2 的 id 相同,都是 10,而实例 3 的编号为 11.

本文介绍了 ABAP 单例模式中一个不为人熟知的知识点:可序列化 Tag 接口 IF_SERIALIZABLE_OBJECT,算是给 ABAP 单例模式的实现,开了一个口子。然而避免踏入这个坑的措施也很简单,只要一个 ABAP 类没有被赋予 IF_SERIALIZABLE_OBJECT 接口,就不会出现本文描述的这种单例行为被破坏的问题。


注销
1k 声望1.6k 粉丝

invalid