简单工厂不简单

马丁花背着灰色的双肩背包走进了公司写字楼,浮现在眼前的是排成长蛇状的一群上班族,正按照单双层各自分成两队等候着电梯。低楼层的童鞋等不及了,甩开双腿开始爬楼梯,就当是减肥锻炼身体了,不着急的依旧排在队伍里,低头看着手机,玩着游戏或者刷着朋友圈。手机扫去了等待的急躁与不安,一个个安静如花,只待电梯像殷勤的主人把他们一批一批接走。

终于踏进了电梯,马丁花被挤到了角落里,电梯里的人互不相识,肃穆像开追悼会,只听得电梯不断发出“叮”的一声响,电子合成声开始汇报多少层到了,门打开,随着不断有人走出,空间变得逐渐开阔起来。

“十八层到了!”自从新搬到这栋写字楼后,马丁花每次听到呆板而不带感情的电子合成声汇报所在楼层到达时,都忍不住翻一下白眼,心里不由脑补出“地狱”两个字眼儿。摇摇头,缓缓走出电梯。

刷卡。自动门刚一打开,就瞥见一道身影正要到正前方拐角处的茶水间倒水,看见自己来了,又赶紧退了回去,似乎故意躲着马丁花。这道身影再熟悉不过,自然躲不过马丁花敏锐的法眼,赶紧叫到:“蔡了!”

“啊……这么倒霉,一上班就被头儿盯住了,昨天布置的作业没完成,怎么办!”蔡了内心不由哀嚎,听到马丁花的声音,只得停下来,无奈装出一副笑脸,言不由衷地说道:“老大,早上好啊!”

“躲着我啊!昨天的作业没做,对吧?”马丁花看到她一副强颜欢笑的样子,哪还不知道古怪灵精的她打的甚么主意,即刻开启了严师模式。

“哼,勤奋如我怎么会没有完成作业呢?”蔡了赶紧辩解,“——只是,我虽然搞清楚了静态工厂实际用的是简单工厂模式,却不知道它能给设计带来什么好处?查了好多资料,发现居然有各种各样的工厂模式,不看还好,越看越糊涂了!”

“嗯,看来你还是做了些功夫的。一会开完站会,到我工位来,我给你简单讲一讲吧!”

蔡了点点头,谢过马丁花,径直到茶水间倒水去了。

站会一开完,蔡了就规规矩矩前来受教。马丁花开门见山,直截了当地说到:“工厂在设计模式中,其实就是一种比喻,目的自然是为了更好地创建对象。既然在Java中已有构造函数能够担负实例化的职责,为何还需要引入工厂呢?要明白工厂的价值,你就需要先明确构造函数的缺陷,对吧?”蔡了不由得点头称是,似乎得到了一丝启发。

“你想想,Java语法中构造函数是怎么定义的?它有哪些语法上的限制?”马丁花循循善诱地提出了问题。

蔡了在脑海中搜索着这段时间学习的Java语法,心里组织着语言,回答道:“类的构造函数与类名必须保持一致;构造函数支持方法重载,但不允许出现相同方法签名;每个具体类的构造函数都只能创建类自身;如果定义了带参构造函数,且又需要无参构造函数,还需要显式定义无参的构造函数。”

“不错!语法记得很清楚。那么,根据这些语法限制,你想想:如果需要创建代码给出清晰的创建目的,构造函数能不能做到?如果创建的对象有可能发生变化,构造函数能不能做到?如果希望控制对象的创建逻辑,构造函数能不能做到?”

“好像是这么回事,不过大叔啊,你可否给我个例子,这样枯燥的理论讲解让人很难理解呢。”小姑娘有些得寸进尺了。

“好吧。”马丁花看着她一脸困惑的样子,无奈地摇摇头,打开IntelliJ,找到JDK中的Optioanal<T>类,展示给蔡了:

“你看JDK 8提供的Optional<T>类,它就定义了诸如empty()of()ofNullable()这几个简单工厂,它们创建的虽然都是Optional<T>对象,可相较于直接调用构造函数,它们有什么价值?”

蔡了陷入沉思中。马丁花提醒道:“还是昨天那句话,你要学会站在调用者的角度看待API设计。”这么一说,小姑娘就回过味来,说道:“对啊,有了这些简单工厂,就能更加清晰地告诉调用者究竟创建了什么样的Optional对象,尤其那个empty(),实在太传神了!这实际上就是可读性的要求嘛,很好理解哟。”马丁花看着她一副不屑一顾的样子,想到她刚才一脸茫然抱怨不好理解,真恨不得给她脑门上来一记头粟!

蔡了倒是没来得及察言观色,继续想着老马刚才问的几个问题,想不通,只得问道:“嘿,大叔,你刚才说控制对象的创建逻辑,我有些不清楚,构造函数就是方法啊,难道还不能控制创建的逻辑?”

“如果创建逻辑只是验证、组装、计算等逻辑,构造函数确实办得到;但是,你注意到没有,Java类的构造函数是没有return的,如果你希望控制对象创建的次数,又或者希望引入缓存提升创建对象的性能,构造函数还能做到吗?更不用说,在有些情况下,如果需要通过反射创建对象,就更需要引入静态工厂方法来封装对象创建的逻辑了。”

“例如有Composer继承体系。”马丁花绘制出如下类图:

“考虑Composer的扩展性,需要通过反射创建各个Composer实现类,类名遵循格式:文件扩展名+Composer,你想想看,可以通过YamlComposer或其他实现类的构造函数来完成吗?”

蔡了使劲摇头!马丁花把一段示例代码打开,指着代码继续讲道:“这个时候,就需要引入一个专门的工厂类,为其定义一个静态工厂方法,用来封装创建逻辑。”

“大叔!”蔡了认真看着这个案例,忽然灵机一动,想到一点,赶紧指出来:“那你说的创建对象有可能发生变化,是不是也属于这种情况啊?”

“孺子可教!”马丁花很高兴看到蔡了能够触类旁通,继续讲道:“刚才说到的Composer对象的创建之所以引入反射,确实考虑到了创建对象的变化,不过,在多数情况下,不一定要使用反射来解决这一问题。只要你希望调用者无需了解产品对象创建变化的逻辑,在工厂方法返回父类产品的前提下,你都可以使用简单工厂。例如JDK中的Collections类,就定义了许多静态工厂方法,用于创建Collection<T>对象。创建的这些对象可能是不同的集合类型,但对于调用者而言,可以不必知道产品对象的具体类型。”马丁花一边说着,一边打开Collections类的定义,将光标快速移到unmodifiableCollection()方法上:

“你看!unmodifiableCollection()方法表面上返回的是一个Collection<T>对象,实际上,在其内部定义了一个内部类UnmodifiableCollection,它屏蔽了集合的修改功能,使之成为一个不可修改的集合。你想想看,这样做的好处是什么?”

“嗯……”思索良久,蔡了想到了答案:“我想,这样做应该有两个好处。一个好处是unmodifiableCollection()方法清晰地向调用者传递了创建不可变集合的语义,另一个好处是将来如果修改了UnmodifiableCollection内部类的定义与实现,也不会影响到该工厂方法的调用者。我说的对吗,马大叔?”

“不错,不错!我看你彻底理解了简单工厂的含义!”

“噢耶!”得到马大叔的肯定,蔡了不由得欢呼起来,不过想到其他工厂模式,又苦下脸来,说道:“可是——我还是不太明白简单工厂模式与工厂方法模式、抽象工厂模式的区别和使用场景呢。”

马丁花想了想,指着茶水间,向蔡了说道:“说了半天,口也渴了,不如我们去那边冲一杯咖啡,边喝边讲,如何?”蔡了点点头,和马丁花一起走到了茶水间的咖啡机旁。