Java設計模式----工廠模式
相信大家和我一樣,在實際開發應用過程中,很難看代碼中有直接new對象的情況,在一開始,也會被前輩告誡,盡量不要使用new來構造對象,盡量使用工廠方法獲取對象雲雲。但是,我相信很多人和我一樣,一開始是一知半解的狀態,憑什麽不能這樣做,用工廠方法獲取對象,多此一舉吧?但是隨著開發經驗的逐漸累積,我發現將構造對象的邏輯暴露給客戶端是十分不合適的設計,一方面你構造該對象的類中,可能並沒有該類構造器需要的參數,這樣就需要你單獨創建這些參數;另一方面,構建對象的邏輯在很多類中使用,勢必造成大量重復代碼;最讓人難受的是,如果你創建的對象的構造方法發生變更,那麽,你就需要修改所有使用構建邏輯的類了,不便於維護。綜合來說,我需要解決如下幾個問題:
- 如何將創建某對象的代碼與業務邏輯相分離
- 如何通過一個統一的接口創建對象
- 如何封裝創建對象的邏輯
工廠模式就為了解決這種創建對象行為而總結出來的設計模式。
1.簡單工廠模式
1.1 介紹
顧名思義,簡單工廠模式實現起來非常簡單明了,雖然不是GoF提到的23種模式之一,但是簡單工廠模式是非常有用的模式。假設在編程中,需要創建不同的圖形,采用簡單工廠模式做如下結構設計:
定義了一個抽象父類Shape, 三個子類繼承於父類Shape, 現在想要使用ShapeFactory來獲取想要的圖形。
1.2 簡單代碼實現
定義了Shape抽象類以及其派生類:
abstract class Shape {public String toString(){ return getClass().getSimpleName(); } } class Circle extends Shape { } class Triangle extends Shape { } class Rectangle extends Shape { }
定義簡單工廠類,提供靜態方法createShape(String type)返回對象引用, 該方法內有多個條件語句分支來判斷用戶需要創建哪個對象。
class ShapeFactory { public static Shape createShape(String type) {if ("circle".equals(type)) { return new Circle(); } else if ("triangle".equals(type)) { return new Triangle(); } else if ("rectangle".equals(type)) { return new Rectangle(); } else { return null; } } }
客戶端代碼
public class SimpleFactoryDemo1 { public static void main(String[] args) { Shape shape = ShapeFactory.createShape("circle"); System.out.println(shape); } }
可以看到,由於判斷對象類型的邏輯放到了工廠中,用戶不再需要知道創建對象的具體構造方法了,以後修改就只需要更改工廠即可。但是,對於這種實現方式,有個問題,就是在createShape方法中,有多個條件判斷分支,如果新增一個Shape的子類,就需要增加一個分支,這中做法,違反了所謂開放-封閉原則,即對擴展開放,對修改關閉。
一般,遇見有較多條件分支語句的代碼,都應該想一想是不是有辦法來解決,反射就是一個好辦法,反射能根據Class對象,動態地創建出其所“描繪”的對象,比如通過Circle.class的Class對象創建出Circle對象來。這裏面,我們把工廠類修改為如下形式:
class ShapeFactory2 { /** 工廠對象,采用單例模式 */ private static ShapeFactory2 factory = new ShapeFactory2(); private ShapeFactory2(){} public static ShapeFactory2 instance() { return factory; } private Map<String, Class<? extends Shape>> classMap = new HashMap<>(); /** 註冊對象信息 */ public void register(String id, Class<? extends Shape> shape) { classMap.put(id, shape); } /** * 獲取對象 */ public Shape createShape(String id) throws Exception { Class<? extends Shape> shapeClass = classMap.get(id); Shape shape = shapeClass.newInstance(); return shape; } }
引入反射機制後,createShape()方法中,就不再有條件分支結構的判斷了。相應的,需要首先註冊該工廠所需的對象信息,註冊方式有很多種,可以在項目啟動時合適的地方設置靜態代碼塊進行批量註冊,也可以在每一個具體圖形class加載的時候進行註冊,本例子采用後者, 對圖形類稍作修改:
class Circle extends Shape { static { ShapeFactory2.instance().register("circle", Circle.class); } } class Triangle extends Shape { static { ShapeFactory2.instance().register("triangle", Triangle.class); } } class Rectangle extends Shape { static { ShapeFactory2.instance().register("rectangle", Rectangle.class); } }
這時,需要保證圖形class對象需要提前被加載, 我在客戶端調用前,在靜態代碼塊中將class加載了。
public class SimpleFactoryDemo1 { static { try { Class.forName("blog.design.factory.Circle"); Class.forName("blog.design.factory.Triangle"); Class.forName("blog.design.factory.Rectangle"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { Shape shape = ShapeFactory2.instance().createShape("circle"); System.out.println(shape); shape = ShapeFactory2.instance().createShape("rectangle"); System.out.println(shape); } }
反射機制引入簡單工廠模式後,就避免了大量的條件分支語句,遵守了開放-封閉原則,而且采用單例模式創建工廠對象,保證系統中只有一套classMap, 便於註冊和消除某圖形對象。
2.工廠方法模式
2.1介紹
對於簡單工廠模式加反射機制,避免了大量條件語句的判斷,但是如果對於某個Shape比如Circle的構造器修改了,那麽還是需要去修改工廠的代碼加入條件分支判斷,工廠方法模式主要為了:
- 為不同對象提供各自的工廠,從而有針對性的實現構造邏輯
- 提供統一的構造接口,把具體構造實現推遲到子類工廠去完成
工廠方法模式(Factory Method Pattern),定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法模式使一個類的實例化延遲到子類。 ---- 《大話設計模式》
結構圖:
2.2代碼實現
改造Shape子類,註意Circle的構造方法與其它兩個子類不同
abstract class Shape { public String toString(){ return getClass().getSimpleName(); } } class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } } class Triangle extends Shape { } class Rectangle extends Shape { }
定義工廠接口和工廠方法,並實現之, 不同工廠實現創建不同對象
interface IFactory { Shape createShape(); } class CircleFactory implements IFactory { private static Random rand = new Random(47); @Override public Shape createShape() { double radius = rand.nextDouble() * 10; return new Circle(radius); } } class RectangleFactory implements IFactory { @Override public Shape createShape() { return new Rectangle(); } } class TriangleFactory implements IFactory { @Override public Shape createShape() { return new Triangle(); } }
客戶端調用
public class FactoryMethodDemo { public static void main(String[] args) { IFactory cFactory = new CircleFactory(); Shape circle = cFactory.createShape(); System.out.println(circle); IFactory rFactory = new RectangleFactory(); Shape rectangle = rFactory.createShape(); System.out.println(rectangle); IFactory tFactory = new TriangleFactory(); Shape triangle = tFactory.createShape(); System.out.println(triangle); } }
輸出
Circle
Rectangle
Triangle
工廠方法模式使代碼進一步解耦,因為講實現邏輯推遲到了子類工廠,所以能針對不同的目標類使用不同的實現邏輯。但是工廠方法模式也有局限:
- 獲取每個對象前,都需要確保相應工廠已經實例化了
- 如果增加新的想要創建的類,就需要增加新的工廠實現,可能導致項目中的類的數量劇烈增長
- 因為工廠類太多了,導致維護和Debug的時候不方便
3.抽象工廠模式
3.1 介紹
最初接觸到工廠方法模式和抽象工廠模式的時候,總是不容易區分其到底有什麽區別,按照我的理解,可以總結為以下兩條:
- 工廠方法模式通常是通過一個工廠方法,創建同一類對象;抽象工廠模式是通過一個接口中不同的工廠方法,創建不同的對象,而這些對象之間通常有一定的依賴關系或關聯關系。(當然抽象工廠方法也可以只創建一類對象,不過即便如此,也不能說抽象工廠模式就變成了工廠方法模式,因為它們的側重點不同,所擴展的方向也就不同);
- 抽象工廠模式,總是應用在同一主題不同平臺、版本等的對象獲取,工廠方法模式通常是獲取相同平臺、版本下的同一類對象。
抽象工廠方法(Abstract Factory Pattern),提供一個創建一系列相關或者相互依賴對象的接口,而無需指定它們具體的類。
結構圖如下:
3.2代碼實現
假設在程序中,需要“畫筆”和“畫板”對象,而我們的app是支持windows和linux的,兩者的實現不同,這樣就需要一個抽象工廠去獲取畫筆和畫板。
畫筆、畫板抽象類
abstract class Pan{ public String toString(){ return getClass().getSimpleName(); } } abstract class Panel { public String toString(){ return getClass().getSimpleName(); } }
windows和linux平臺下的畫筆、畫板
class WindowsPan extends Pan { public void paint(){ System.out.println(this + " is painting!"); } } class LinuxPan extends Pan { public void paint(){ System.out.println(this + " is painting!"); } } class WindowsPanel extends Panel { } class LinuxPanel extends Panel { }
抽象工廠類
abstract class PaintingFactory { abstract Pan createPan(); abstract Panel createPanel(); }
windows和linux 下的具體工廠,生產相應環境下的畫筆和畫版
class WindowsPaintingFactory extends PaintingFactory{ @Override Pan createPan() { return new WindowsPan(); } @Override Panel createPanel() { return new WindowsPanel(); } } class LinuxPaintingFactory extends PaintingFactory { @Override Pan createPan() { return new LinuxPan(); } @Override Panel createPanel() { return new LinuxPanel(); } }
客戶端調用,假設當前環境是windows系統
public class AbstractFactoryDemo { //假設當前環境為windows操作系統 public static final String OPERATING_SYSTEM = "windows"; static PaintingFactory getFactory(){ if (OPERATING_SYSTEM.equals("windows")) { return new WindowsPaintingFactory(); } else if (OPERATING_SYSTEM.equals("linux")) { return new LinuxPaintingFactory(); } return new WindowsPaintingFactory(); } public static void main(String[] args) { //獲取當前環境下的工廠 PaintingFactory factory = getFactory(); Pan pan = factory.createPan(); Panel panel = factory.createPanel(); pan.paint(); System.out.println(panel); } }
輸出結果
WindowsPan is painting!
WindowsPanel
抽象工廠模式還經常應用於不同數據庫管理系統的數據源對象的創建過程。與工廠方法模式類似,當擴展的類過多時,將難以Debug。
Java設計模式----工廠模式