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


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

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

    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对象就可以调用具体子类里面的操作方法。


    三种模式的适用场景

    • 简单工厂模式之所以没有被官方收入,是因为它只是用来学习入门工厂模式的,在实际工作中,很少被使用。
    • 工厂方法模式更适合那种子类之间并没有直接关联关系的结构,没办法,只能每种产品建立一个独立的工厂去创建。
    • 抽象工厂模式就适合那种子类之间有明显的关联关系的结构,将他们拆分归并,仅给一批关联在一起的子类建立一个独立的工厂,它可以批量生产出这批子类的所有实例。
  • 相关阅读:
    Zookeeper入门
    Redis五种数据类型
    shardingJDBC分库分表
    RabbitMQ入门
    Spring-Boot
    spring-mvc
    oracle一些对表的操作命令
    对IFeatureClass的选择结果进行再次选择及排序
    关于基础 DBMS 错误 ORA-01654的总结
    Linq 如何实现 in 与 not in
  • 原文地址:https://www.cnblogs.com/Evsward/p/factory.html
Copyright © 2020-2023  润新知