zoukankan      html  css  js  c++  java
  • 精雕细琢——全方位解析工厂模式

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

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

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


    三种模式的适用场景

    • 简单工厂模式之所以没有被官方收入,是因为它只是用来学习入门工厂模式的,在实际工作中,很少被使用。
    • 工厂方法模式更适合那种子类之间并没有直接关联关系的结构,没办法,只能每种产品建立一个独立的工厂去创建。
    • 抽象工厂模式就适合那种子类之间有明显的关联关系的结构,将他们拆分归并,仅给一批关联在一起的子类建立一个独立的工厂,它可以批量生产出这批子类的所有实例。
  • 相关阅读:
    Spring Cloud Hystrix Dashboard的使用 5.1.3
    Spring Cloud Hystrix 服务容错保护 5.1
    Spring Cloud Ribbon 客户端负载均衡 4.3
    Spring Cloud 如何实现服务间的调用 4.2.3
    hadoop3.1集成yarn ha
    hadoop3.1 hdfs的api使用
    hadoop3.1 ha高可用部署
    hadoop3.1 分布式集群部署
    hadoop3.1伪分布式部署
    KVM(八)使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机
  • 原文地址:https://www.cnblogs.com/Evsward/p/factory.html
Copyright © 2011-2022 走看看