精雕细琢——全方位解析工厂模式

工厂模式是面向对象设计模式中非常重要,非常流行的模式,是应该首先被理解透彻的模式。

我们讲对象的相关职责包括:

  1. 对象本身的职责(数据和行为)
  2. 创建对象的职责
  3. 使用对象的职责

而对象的创建在Java中有四种方式:

  1. new
  2. 反射
  3. clone()
  4. 工厂类创建

工厂模式是创建型设计模式

程序员的敏感地带:

  1. 大量的重复性代码,大量的if...else...语句
  2. 一个类过于复杂,违反了“单一职责原则”
  3. 如果有扩展会引发修改,违反了“开闭原则”
  4. 使用对象时不要用new来创建对象,耦合度高

我们先描述一个场景,某产品展销会上,用户甲需要了解多种产品的详细情况,他需要自己去根据每个产品名字在地图上查找其陈列位置,然后跑过去挨家看。而此时,待在家里的用户乙也想了解一下这个展销会里面的产品,他给举办此次展销会的厂商丙打了个电话,厂商接待人员说,“你想了解哪个产品,把名字告诉我,我直接就告诉那个产品的情况。”

  • 用户甲自己new完成了产品实例的查找及创建,然后调取了产品的内部详细信息。总共只有一个类,丝毫没有设计可言,所有代码堆砌在这个类中,维护性,灵活性,扩展性,复用性全为0。
  • 用户乙通过工厂类丙创建的方式获得了产品的实例,然后调取了产品的内部详细信息。

上面提到了对象的三种职责,根据“单一职责原则”。

两个类A和B之间的关系应该仅仅是A创建B或者A使用B,而不能是两种都有。

工厂模式就是要增加一层厂商丙来统一管理对象的创建职责,而不是让对象实例化的代码掺杂在多个类中到处都是。


厂商丙的多种实现方式之一:简单工厂模式

定义一个工厂类,增加一个静态方法(所以也叫静态工厂模式),传入不同的参数,内部根据这个参数进行判断,返回不同类的实例,这些被创建实例的类通常都有共同的父类。我们看一下在简单工厂模式中,用户是如何获取一个实例化对象的。

class User {  
    public static void main(String args[]) {
        Book book1 = Factory.createBook("novel");  
        book1.read();
    }
}

而工厂类的内容是:

class Factory {  
    public static Book createBook(String bookType) {
        if("novel".equals(bookType){
            return new NovelBook();  
        }else if("poem".equals(bookType){
            return new PoemBook();  
        }else if("history".equals(bookType){
            return new HistoryBook();  
        }else{
            return null;
        }
    }
}

缺陷:

  1. 工厂类内部复杂,有大量if...else...判断
  2. 扩展要修改工厂类中的if...else...语句,违反“开闭原则”

厂商丙的多种实现方式之二:工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 我们看一下在工厂方法模式中,用户是如何获取一个实例化对象的。

class User {  
    public static void main(String args[]) {
        Factory factory = new NovelBookFactory();
        Book book1 = factory.createBook();
        book1.read();
    }  
}

稍作解读,就是在简单工厂模式里的工厂类的基础上又抽象出一层,将其改为工厂接口Factory,声明一个工厂方法factoryMethod(),而具体产品要有对应的工厂类去实现Factory接口,并重写工厂方法返回对应的具体产品实例。将实例化时机又转移给了用户,用户来决定用哪个具体产品实例。

interface Factory {  
    Book creatBook();
}
class NovelBookFactory implement Factory{  
    Book creatBook(){
        return new NovelBook();
    }
}

这与前面提到的场景中,用户甲也是自己判断然后创建实例是截然不同的,因为工程方法只是让用户决定实例化哪个具体产品,而不会让他去创建,创建的工作还是交给工厂,所以在扩展的时候,当前工厂类,产品类都是不用改动的,只需要再增加一对具体工厂类和具体产品即可。

缺陷:

  • 每次扩展需要增加一对具体工厂类和具体产品类,久而久之,会造成程序中类的数量爆炸。

问题:我们为什么不这样写?

class User {  
    public static void main(String args[]) {
        Book book1 = new NovelBook();
        book1.read();
    }  
}

看上去与上面的工厂方法模式的使用没有任何区别,扩展的时候,也只是需要新增一个子类即可,符合“开闭原则”,但是,这样写是不是就违反了“单一职责原则”呢?我们希望能将具体对象的创建职责通过工厂去管理起来,使程序的可读性更高,也降低类之间的耦合度,不在使用对象时直接new其实例。

厂商丙的多种实现方式之三:抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 接着上面的代码改造一下。

class User {  
    public static void main(String args[]) {
        Factory factory = new LuXunBookFactory();
        Prose prose1 = factory.creatProseBook();
        Essay essay1 = factory.creatEssayBook();
        Novel novel1 = factory.creatNovelBook();
        prose1.read();
        essay1.read();
        novel1.read();
    }  
}
class LuXunBookFactory implement Factory{  
    Prose creatProseBook(){//散文
        return new ProseBook();
    }
    Essay creatEssayBook(){//杂文
        return new EssayBook();
    }
    Novel creatNovelBook(){
        return new NovelBook();
    }
}

相较于工厂方法模式,抽象工厂模式将具体工厂类中的具体产品类进行了组合的关联。如上面代码所示,用户要得到的是鲁迅全集的实例,鲁迅全集工厂类就要包括散文,小说和杂文,而这些都是书的子类。如果是工厂方法模式的话,恐怕就要每种书记形式都要建一个自己的工厂类,那实在是庞大至极。程序变得很冗长。所以抽象工厂模式在具体子类中具备一定的关联关系的时候,非常好用。 缺陷:

  1. 抽象工厂模式由于将系统又分出一层,利用工厂去管理归并后的“鲁迅全集”,而不是单独管理每个具体类,当扩展时,再出个也是包括散文、小说和杂文的“老舍全集”还好说,只要新增一个老舍全集的类,和一个老舍全集工厂类就好。但是如果“老舍全集”多了诗歌,评论等,那就灾难了,要去修改工厂基类Factory的方法声明,这就违反了“开闭原则”。抽象工厂模式在你确定使用它的那一刻,就要十分确定这个工厂类内部子类的结构是稳定的,不会改变的,否则就不要使用抽象工厂,去用工厂方法模式或许更适合。

我们看到了上面用户在使用工厂模式创建实例时,避免了直接new具体对象实例的方式,但是能否把new LuXunBookFactory()也避免了呢?进一步解耦? 答案是肯定的。

使用反射加配置文件代替new LuXunBookFactory()

我们在创建一个实例的时候,能否直接用字符串本身当做参数来创建对象呢?使用反射就可以达到这个目的。

Factory factory = (Factory) Class.forName("LuXunBookFactory").newInstance();

这样就代替了

Factory factory = new LuXunBookFactory();

那么如果要完全符合“开闭原则”,即用户使用的部分也不去修改,那么就可以采用配置文件的方式。 将字符串"LuXunBookFactory"保存在xml配置文件中。

<?xml version="1.0"?>  
<config>  
    <className>LuXunBookFactory</className>  
</config> 

每次利用Java工具XMLUtil类去读取该配置文件,用变量代替原代码中字符串的位置。

工厂方法的隐藏

我们将上面的工厂方法模式用户操作部分粘贴过来看一下:

class User {  
    public static void main(String args[]) {
        Factory factory = new NovelBookFactory();
        Book book1 = factory.createBook();
        book1.read();
    }  
}
interface Factory {  
    Book creatBook();
}
class NovelBookFactory implement Factory{  
    Book creatBook(){
        return new NovelBook();
    }
}

我们发现Book中的read方法应该是直接在基类中声明,所有Book子类去重写具体book方法。那么在用户使用时,这个book方法的调用方法就可以抽象到Factory中去。 当前Factory是接口,接口是无法写方法体的,因此要改为abstract class。

abstract class Factory{
   abstract Book creatBook();
   public void read(){
        Book book = this.creatBook();
        book.read();
    }
}

那么此时,用户在操作时就可以改为

class User {  
    public static void main(String args[]) {
        Factory factory = new NovelBookFactory();
        factory.read();
    }  
}

具体产品工厂类NovelBookFactory中并不需要去管藏方法的事,藏方法一定是在abstract class中放一个具体方法,用于获取当前抽象方法创建的具体子类实例后,直接调用其操作方法。 这样一来,在外部操作的时候,直接用Factory对象就可以调用具体子类里面的操作方法。


三种模式的适用场景

  • 简单工厂模式之所以没有被官方收入,是因为它只是用来学习入门工厂模式的,在实际工作中,很少被使用。
  • 工厂方法模式更适合那种子类之间并没有直接关联关系的结构,没办法,只能每种产品建立一个独立的工厂去创建。
  • 抽象工厂模式就适合那种子类之间有明显的关联关系的结构,将他们拆分归并,仅给一批关联在一起的子类建立一个独立的工厂,它可以批量生产出这批子类的所有实例。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python小屋

Python字符串处理小案例

连续5天30个小时的Python培训圆满结束,明天早上5点半出发赶飞机回烟台,晚上收拾行李的时候突然想起来20年前做过的一个C语言题目:假设有一个字符串,里面有...

30450
来自专栏老马说编程

计算机程序的思维逻辑 (14) - 类的组合

上节我们通过类Point介绍了类的一些基本概念和语法,类Point中只有基本数据类型,但类中的成员变量的类型也可以是别的类,通过类的组合可以表达更为复杂的概念。...

23390
来自专栏java学习

Java每日一练(2017/7/20)

最新通知 ●回复"每日一练"获取以前的题目! ●【新】Ajax知识点视频更新了!(回复【学习视频】获取下载链接) ●【新】HTML5知识点视频更新了!(回复【前...

26660
来自专栏微信公众号:Java团长

Java基础04 封装与接口

总结之前的内容,对象(object)指代某一事物,类(class)指代象的类型。对象可以有状态和动作,即数据成员和方法。

16720
来自专栏web编程技术分享

JavaScript: 零基础轻松学闭包(1)

20750
来自专栏Vamei实验室

Java基础04 封装与接口

总结之前的内容,对象(object)指代某一事物,类(class)指代象的类型。对象可以有状态和动作,即数据成员和方法。 到现在为止,数据成员和方法都是同时开放...

21770
来自专栏CVer

资源 | 十分钟速成Python

Amusi 今天不推论文速递、不推论文精读,不聊知识点。Amusi 知道关注CVer的同学们都是机器学习、深度学习、CV或者NLP方向的研究者,毋庸置疑,Pyt...

24230
来自专栏java一日一条

Java面试参考指南(一)

Java是一种基于面向对象概念的编程语言,使用高度抽象化来解决现实世界的问题。 面向对象的方法将现实世界中的对象进行概念化,以便于在应用之间进行重用。例如...

19330
来自专栏程序员互动联盟

【编程基础】盖大楼地基要牢固

水之积也不厚,则其负大舟也无力。——庄子 上一篇讲了几个编译编辑器,大家都可以用用,新手掌握几个是没有坏处的。 学编程要从基础学起,就像盖大楼,先把地基打好,...

37490
来自专栏Java 源码分析

数据结构Generic

​ 接下来我们要处理的是前面实现里另一个 根本性的缺陷 那些实现只适用于字符串,想要实现其他类型数据的队列和栈怎么办呢? 这个问题就涉及泛型的话题了。 ​...

34340

扫码关注云+社区

领取腾讯云代金券