讨论实际项目的工厂模式

大思接了满满一杯热咖啡,正欲离去,转身时,偶然瞥见茶水间的白板画满了类图,定睛一看,感慨地说道:“蔡了,你还真是幸福啊!马大叔亲自给你讲解工厂模式。”

蔡了刚刚意识到自己说错了话,还在尴尬中,赶紧说道:“是啊,是啊!”一边说着,一边还使劲地点着头,希望通过过分的礼貌来化解刚才的口不择言。

成大思想到了自己过去学习设计模式的经历,悠悠地说道:“说起来,当初我学设计模式时,可没有你这么好的待遇了。”

这一番话倒是勾起了蔡了的好奇心,赶紧问道:“大思,那你当时是怎么学的呢?”

既然有了谈兴,成大思也在沙发坐下来,慢条斯理地说道:“当初我刚入行没多久,就接到一个开发任务,让我独自完成一个报表组件的设计与开发。这一报表组件是公司应用框架的一部分,需要支持微软的水晶报表和用友的华表。说起来,现在的程序员可能都没听说过这两款报表产品了吧。”谈到过去,同样作为职场老人的马丁花似乎深有同感,举起手里的咖啡,如饮酒一般向大思隔空敬过,二人各自仰头吞了大大一口苦涩的浓咖啡,长吁短叹,对视无言,油然而生英雄迟暮之感。

95后的蔡了哪里能体悟IT历史的往昔与荣光,继续如好奇宝宝地追问:“后来呢?”

大思回过神,继续讲道:“我接到任务后,想了许久,也不知该如何设计才能灵活地支持各种报表,无奈之下,只得去寻求项目经理的帮助。项目经理听了我的问题,丢下一句‘用工厂模式可以解决’,然后就酷酷地抛下我不管了。没有办法,好歹给我指点了明灯,于是我就开始上穷碧落下黄泉地寻找工厂模式的资料。那时候,哪有这么多讲解设计模式的资料?主要的参考资料还是GOF的《设计模式》。说起来,虽然这本书的讲解稍显晦涩,但它才是设计模式的正宗心法啊!蔡了,你可以好好读读这本书!”

蔡了谢过大思的推荐,继续追问:“那你最后是如何设计的呢?”

“真要理解了工厂方法模式,说起来也不难。我对这两种报表进行了抽象,分别定义了报表对象(ReportObject)、报表处理器(ReportProcessor)和报表格式器(ReportFormatter),毫无疑问,它们都具有各自的继承体系,可以引入工厂来创建它们。”

说到这里,大思拿起板擦,问道:“白板上的内容可以擦掉吧?”这时的蔡了倒是学会了察言观色,赶紧抢过大思手中的板擦,说道:“我来!我来!”三下五除二,白板擦得干干净净。大思在白板上画下了这样的类图:

“这不就是抽象工厂模式吗?”蔡了惊喜地嚷道。

“不错!”成大思答道,“说起来,这一设计方案都过去十多年了,我至今对这一方案依旧历历在目,毕竟这是我第一次独立设计一个组件。”

“所以说,还是要做实际的项目才能锻炼人!”马丁花补充道,然后对蔡了道:“听别人讲授设计知识固然能快速帮助你理清思路,掌握知识窍门,但没有经过你自己的思考,不可能真正掌握,更不用说合理地运用到项目中。这样吧,给你布置一个学习任务,利用业余时间学习slf4j日志框架。我们项目也使用了这一框架,你应该知道它的用法,对吧?”

蔡了点头称是。

马丁花继续说道:“slf4j框架灵活地运用了设计模式,也包括今天提到的工厂模式。针对slf4j如何创建一个Logger,你可以查阅相关资料,并阅读slf4j的源代码,深入了解其设计手段,写一份学习笔记。”

三人结束了这番在茶水间的谈话,留给蔡了的又是一份没法拒绝的学习礼物。


三天后,蔡了向马丁花交付了一份学习笔记。内容如下:

slf4j本身是一个日志框架,为了让Java开发人员可以采用同一种方式使用日志,它又很好地集成了logback、log4j等其他日志框架。为此,它定义了标准的日志接口,slf4j的slf4j-simple日志框架与logback都实现了日志接口。由于log4j的诞生要早于slf4j,则专门提供了slf4j-log4j12将log4j整合到slf4j中。

不管是什么样的日志框架,只要使用slf4j,都可以通过如下代码来创建Logger


private final Logger logger = LoggerFactory.getLogger(Demo.class);

LoggerFactorygetLogger()方法就是简单工厂模式的体现。在getLogger()方法内部,具体创建什么样的Logger,又由ILoggerFactory工厂来决定的。ILoggerFactory工厂和它创建的Logger就是slf4j定义的通用日志对象接口。以logback为例,它定义了自己的日志工厂和日志对象,实现了slf4j的接口,如果其他日志框架,如log4j需要slf4j作为统一的日志入口,也需要实现这些接口。

这实际上是工厂方法模式的体现。

slf4j具体使用了哪一个日志框架,取决于项目依赖的日志包。只要将对应的日志系统jar包加入到项目中,slf4j就会自动选择使用它。例如,倘若添加这样的Maven依赖:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<optional>true</optional>
<scope>runtime</scope>
</dependency>

意味着slf4j会使用logback日志框架。

ILoggerFactory对象的创建由slf4j来决定,不需要进行配置,更不需要人为的修改创建代码。怎么才能做到这一点呢?

我仔细阅读了slf4j的源代码,又查阅了相关的资料,发现slf4j具体选择哪一个ILoggerFactory,是由LoggerFactoryBinder决定的,可以将它理解为是日志工厂的工厂,在slf4j框架中,将其形象地称之为一种绑定(bind)操作。

根据这一设计,我最初以为slf4j要通过反射创建不同日志框架的LoggerFactoryBinder对象,后来发现LoggerFactoryBinder接口只有一个实现类,即StaticLoggerBinderLoggerFactory调用了它的getSingleton()方法获得StaticLoggerBinder对象,再由此获得ILoggerFactory工厂:

StaticLoggerBinder.getSingleton().getLoggerFactory()

我翻阅了成大思推荐的《设计模式》,了解到创建StaticLoggerBinder对象的这种方式称之为单例模式。我站在调用者的角度去思考,发现这一设计可以高效地获得StaticLoggerBinder对象,因为它可以避免对象的频繁创建。

由于getSingleton()StaticLoggerBinder的静态方法,因此LoggerFactory对它是类型依赖的,做不到实例方法的多态扩展。实际上,这个类的名称也说明了它采用了静态方式绑定了日志工厂。这就要求所有实现了slf4j接口的日志框架,都需要定义这个类,且这个类的classpath应该保持一致。在slf4j的LoggerFactory中定义了这个类的路径:

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

查看logback的JAR包,确实在指定位置发现了该类:


马丁花收到这份作业后,不禁抓了抓已经日渐稀薄的头发,心里哀叹,要培养一个新人,还有很长的一条路要走。是否让蔡了继续深入研究slf4j这一框架呢?马丁花陷入了沉思中。