深入理解工廠模式

SnailClimb發表於2019-01-19

Java面試通關手冊(Java學習指南,歡迎Star,會一直完善下去,歡迎建議和指導):https://github.com/Snailclimb/Java_Guide

歷史回顧:

深入理解單例模式

歷史文章推薦:

分散式系統的經典基礎理論

可能是最漂亮的Spring事務管理詳解

面試中關於Java虛擬機器(jvm)的問題看這篇就夠了

[TOC]

一 工廠模式介紹

1.1 工廠模式的定義

先來看一下GOF為工廠模式的定義:

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基類中定義建立物件的一個介面,讓子類決定例項化哪個類。工廠方法讓一個類的例項化延遲到子類中進行。)

1.2 工廠模式的分類:

(1)簡單工廠(Simple Factory)模式,又稱靜態工廠方法模式(Static Factory Method Pattern)。

(2)工廠方法(Factory Method)模式,又稱多型性工廠(Polymorphic Factory)模式或虛擬構造子(Virtual Constructor)模式;

(3)抽象工廠(Abstract Factory)模式,又稱工具箱(Kit 或Toolkit)模式。

1.3 在開源框架中的使用

舉兩個比較常見的例子(我暫時可以準確想到的,當然還有很多很多):

(1)Spring中通過getBean(“xxx”)獲取Bean;

(2) Java訊息服務JMS中(下面以訊息佇列ActiveMQ為例子)

關於訊息佇列ActiveMQ的使用可以檢視:訊息佇列ActiveMQ的使用詳解

        // 1、建立一個連線工廠物件,需要指定服務的ip及埠。
       ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.155:61616");
       // 2、使用工廠物件建立一個Connection物件。
       Connection connection = connectionFactory.createConnection();

1.4 為什麼要用工廠模式

(1) 解耦 :把物件的建立和使用的過程分開

(2)降低程式碼重複: 如果建立某個物件的過程都很複雜,需要一定的程式碼量,而且很多地方都要用到,那麼就會有很多的重複程式碼。

(3) 降低維護成本 :由於建立過程都由工廠統一管理,所以發生業務邏輯變化,不需要找到所有需要建立物件B的地方去逐個修正,只需要在工廠裡修改即可,降低維護成本。

關於工廠模式的作用,Mark一篇文章:https://blog.csdn.net/lovelion/article/details/7523392

二 簡單工廠模式

2.1 介紹

嚴格的說,簡單工廠模式並不是23種常用的設計模式之一,它只算工廠模式的一個特殊實現。簡單工廠模式在實際中的應用相對於其他2個工廠模式用的還是相對少得多,因為它只適應很多簡單的情況。

最重要的是它違背了我們在概述中說的 開放-封閉原則 (雖然可以通過反射的機制來避免,後面我們會介紹到) 。因為每次你要新新增一個功能,都需要在生switch-case 語句(或者if-else 語句)中去修改程式碼,新增分支條件。

2.2 適用場景

(1)需要建立的物件較少。

(2)客戶端不關心物件的建立過程。

2.3 簡單工廠模式角色分配:

  1. 工廠(Factory)角色 :簡單工廠模式的核心,它負責實現建立所有例項的內部邏輯。工廠類可以被外界直接呼叫,建立所需的產品物件。
  2. 抽象產品(Product)角色 :簡單工廠模式所建立的所有物件的父類,它負責描述所有例項所共有的公共介面。
  3. 具體產品(Concrete Product)角色:簡單工廠模式的建立目標,所有建立的物件都是充當這個角色的某個具體類的例項。

2.4 簡單工廠例項

建立一個可以繪製不同形狀的繪圖工具,可以繪製圓形,正方形,三角形,每個圖形都會有一個draw()方法用於繪圖.

(1)建立Shape介面


public interface Shape {
    void draw();
}

(2)建立實現該介面的具體圖形類

圓形


public class Circle implements Shape {
    public Circle() {
        System.out.println("Circle");
    }
    @Override
    public void draw() {
        System.out.println("Draw Circle");
    }
}

長方形


public class Rectangle implements Shape {
    public Rectangle() {
        System.out.println("Rectangle");
    }
    @Override
    public void draw() {
        System.out.println("Draw Rectangle");
    }
}

正方形


public class Square implements Shape {
    public Square() {
        System.out.println("Square");
    }

    @Override
    public void draw() {
        System.out.println("Draw Square");
    }
}

(3)建立工廠類:


public class ShapeFactory {

    // 使用 getShape 方法獲取形狀型別的物件
    public static Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        } else if (shapeType.equalsIgnoreCase("SQUARE")) {
            return new Square();
        }
        return null;
    }
}

(4)測試方法:


public class Test {

    public static void main(String[] args) {

        // 獲取 Circle 的物件,並呼叫它的 draw 方法
        Shape circle = ShapeFactory.getShape("CIRCLE");
        circle.draw();

        // 獲取 Rectangle 的物件,並呼叫它的 draw 方法
        Shape rectangle = ShapeFactory.getShape("RECTANGLE");
        rectangle.draw();

        // 獲取 Square 的物件,並呼叫它的 draw 方法
        Shape square = ShapeFactory.getShape("SQUARE");
        square.draw();
    }
}

輸出結果:

Circle
Draw Circle
Rectangle
Draw Rectangle
Square
Draw Square

這樣的實現有個問題,如果我們新增產品類的話,就需要修改工廠類中的getShape()方法,這很明顯不符合 開放-封閉原則

2.5 使用反射機制改善簡單工廠

將工廠類改為下面的形式:

package factory_pattern;

/**
 * 利用反射解決簡單工廠每次增加新了產品類都要修改產品工廠的弊端
 * 
 * @author Administrator
 *
 */
public class ShapeFactory2 {
    public static Object getClass(Class<? extends Shape> clazz) {
        Object obj = null;

        try {
            obj = Class.forName(clazz.getName()).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return obj;
    }
}

測試方法:

package factory_pattern;

public class Test2 {
    public static void main(String[] args) {

        Circle circle = (Circle) ShapeFactory2.getClass(factory_pattern.Circle.class);
        circle.draw();

        Rectangle rectangle = (Rectangle) ShapeFactory2.getClass(factory_pattern.Rectangle.class);
        rectangle.draw();

        Square square = (Square) ShapeFactory2.getClass(factory_pattern.Square.class);
        square.draw();
    }

}

這種方式的雖然符合了 開放-關閉原則 ,但是每一次傳入的都是產品類的全部路徑,這樣比較麻煩。如果需要改善的話可以通過 反射+配置檔案 的形式來改善,這種方式使用的也是比較多的。

3 工廠方法模式

3.1 介紹

工廠方法模式應該是在工廠模式家族中是用的最多模式,一般專案中存在最多的就是這個模式。

工廠方法模式是簡單工廠的僅一步深化, 在工廠方法模式中,我們不再提供一個統一的工廠類來建立所有的物件,而是針對不同的物件提供不同的工廠。也就是說 每個物件都有一個與之對應的工廠

3.2 適用場景

  • 一個類不知道它所需要的物件的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品物件由具體工廠類建立;客戶端需要知道建立具體產品的工廠類。
  • 一個類通過其子類來指定建立哪個物件:在工廠方法模式中,對於抽象工廠類只需要提供一個建立產品的介面,而由其子類來確定具體要建立的物件,利用物件導向的多型性和里氏
  • 將建立物件的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無需關心是哪一個工廠子類建立產品子類,需要時再動態指定,可將具體工廠類的類名儲存在配置檔案或資料庫中。

3.3 工廠方法模式角色分配:

  1. 抽象工廠(Abstract Factory)角色:是工廠方法模式的核心,與應用程式無關。任何在模式中建立的物件的工廠類必須實現這個介面。
  2. 具體工廠(Concrete Factory)角色 :這是實現抽象工廠介面的具體工廠類,包含與應用程式密切相關的邏輯,並且受到應用程式呼叫以建立某一種產品物件。
  3. 抽象產品(AbstractProduct)角色 :工廠方法模式所建立的物件的超型別,也就是產品物件的共同父類或共同擁有的介面。
  4. 具體產品(Concrete Product)角色 :這個角色實現了抽象產品角色所定義的介面。某具體產品有專門的具體工廠建立,它們之間往往一一對應

3.4 工廠方法模式例項

上面簡單工廠例子中的圖形介面以及相關影像實現類不變。我們只需要增加一個工廠介面以及實現這個介面的工廠類即可。

(1)增加一個工廠介面:

public interface Factory {
    public Shape getShape();
}

(2)增加相關工廠類:

圓形工廠類

public class CircleFactory implements Factory {

    @Override
    public Shape getShape() {
        // TODO Auto-generated method stub
        return new Circle();
    }

}

長方形工廠類

public class RectangleFactory implements Factory{

    @Override
    public Shape getShape() {
        // TODO Auto-generated method stub
        return new Rectangle();
    }

}

圓形工廠類

public class SquareFactory implements Factory{

    @Override
    public Shape getShape() {
        // TODO Auto-generated method stub
        return new Square();
    }

}

(3)測試:

public class Test {

    public static void main(String[] args) {
        Factory circlefactory = new CircleFactory();
        Shape circle = circlefactory.getShape();
        circle.draw();
    }

}

輸出結果:

Circle
Draw Circle

4 抽象工廠模式

4.1 介紹

在工廠方法模式中,其實我們有一個潛在意識的意識。那就是我們生產的都是同一類產品。抽象工廠模式是工廠方法的僅一步深化,在這個模式中的工廠類不單單可以建立一種產品,而是可以建立一組產品。

抽象工廠應該是比較最難理解的一個工廠模式了。

4.2 適用場景

  • 和工廠方法一樣客戶端不需要知道它所建立的物件的類。
  • 需要一組物件共同完成某種功能時,並且可能存在多組物件完成不同功能的情況。(同屬於同一個產品族的產品)
  • 系統結構穩定,不會頻繁的增加物件。(因為一旦增加就需要修改原有程式碼,不符合開閉原則)

4.3 抽象工廠方法模式角色分配:

  1. 抽象工廠(AbstractFactory)角色 :是工廠方法模式的核心,與應用程式無關。任何在模式中建立的物件的工廠類必須實現這個介面。
  2. 具體工廠類(ConreteFactory)角色 :這是實現抽象工廠介面的具體工廠類,包含與應用程式密切相關的邏輯,並且受到應用程式呼叫以建立某一種產品物件。
  3. 抽象產品(Abstract Product)角色 :工廠方法模式所建立的物件的超型別,也就是產品物件的共同父類或共同擁有的介面。
  4. 具體產品(Concrete Product)角色 :抽象工廠模式所建立的任何產品物件都是某一個具體產品類的例項。在抽象工廠中建立的產品屬於同一產品族,這不同於工廠模式中的工廠只建立單一產品,我後面也會詳解介紹到。

4.4 抽象工廠的工廠和工廠方法中的工廠有什麼區別呢?

抽象工廠是生產一整套有產品的(至少要生產兩個產品),這些產品必須相互是有關係或有依賴的,而工廠方法中的工廠是生產單一產品的工廠。

4.5 抽象工廠模式例項

不知道大家玩過穿越火線或者吃雞這類遊戲了嗎,遊戲中存在各種槍。我們假設現在存在AK、M4A1兩類槍,每一種槍對應一種子彈。我們現在這樣考慮生產AK的工廠可以順便生產AK使用的子彈,生產M4A1的工廠可以順便生產M4A1使用的子彈。(AK工廠生產AK系列產品包括子彈啊,AK槍的型別啊這些,M4A1工廠同理)

抽象工廠模式例項

(1)建立相關介面:

public interface Gun {
    public void shooting();
}

子彈

public interface Bullet {
    public void load();
}

(2)建立介面對應實現類:

AK類

public class AK implements Gun{

    @Override
    public void shooting() {
        System.out.println("shooting with AK");
        
    }

}

M4A1類

public class M4A1 implements Gun {

    @Override
    public void shooting() {
        System.out.println("shooting with M4A1");

    }

}

AK子彈類

public class AK_Bullet implements Bullet {

    @Override
    public void load() {
        System.out.println("Load bullets with AK");
    }

}

M4A1子彈類

public class M4A1
_Bullet implements Bullet {

    @Override
    public void load() {
        System.out.println("Load bullets with M4A1");
    }

}

(3)建立工廠介面

public interface Factory {
    public Gun produceGun();
    public Bullet produceBullet();
}

(4)建立具體工廠

生產AK和AK子彈的工廠

public class AK_Factory implements Factory{

    @Override
    public Gun produceGun() {
        return new AK();
    }

    @Override
    public Bullet produceBullet() {
        return new AK_Bullet();
    }

}

生產M4A1和M4A1子彈的工廠

public class M4A1_Factory implements Factory{

    @Override
    public Gun produceGun() {
        return new M4A1();
    }

    @Override
    public Bullet produceBullet() {
        return new M4A1_Bullet();
    }

}

(5)測試

public class Test {

    public static void main(String[] args) {  
        
     Factory factory;
     Gun gun;
     Bullet bullet;
     
     factory =new AK_Factory();
     bullet=factory.produceBullet();
     bullet.load();
     gun=factory.produceGun();
     gun.shooting(); 
     
    }

}

輸出結果:

Load bullets with AK
shooting with AK

我是Snailclimb,一個以架構師為5年之內目標的小小白。 歡迎關注我的微信公眾號:”Java面試通關手冊“(一個有溫度的微信公眾號,期待與你共同進步~~~堅持原創,分享美文,分享各種Java學習資源)

最後,就是使用阿里雲伺服器一段時間後,感覺阿里雲真的很不錯,就申請做了阿里雲大使,然後這是我的優惠券地址.

相關文章