工廠設計模式究竟怎麼寫更優雅?!
閒來無事看了菜鳥教程的設計模式。看到了一個很有趣的討論,該討論是關於工廠設計模式的書寫形式。下面先看一下給出的基礎寫法,然後再看一下各位網友的優化。
工廠設計模式初衷:我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。即只需要告訴介面想要獲取物件的型別,然後介面就會建立好該型別對應的物件,並返回。
類圖如:
根據上面的類圖,可以給出如下實現:
1.首先建立shape.java介面
public interface Shape { void draw(); }
2.建立介面的三個實現類:
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } }
public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } }
public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } }
3.建立工廠:
public class ShapeFactory { //使用 getShape 方法獲取形狀型別的物件 public 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 FactoryPatternDemo { public static void main(String[] args) { ShapeFactory shapeFactory = new ShapeFactory(); //獲取 Circle 的物件,並呼叫它的 draw 方法 Shape shape1 = shapeFactory.getShape("CIRCLE"); //呼叫 Circle 的 draw 方法 shape1.draw(); //獲取 Rectangle 的物件,並呼叫它的 draw 方法 Shape shape2 = shapeFactory.getShape("RECTANGLE"); //呼叫 Rectangle 的 draw 方法 shape2.draw(); //獲取 Square 的物件,並呼叫它的 draw 方法 Shape shape3 = shapeFactory.getShape("SQUARE"); //呼叫 Square 的 draw 方法 shape3.draw(); } }
5.輸出結果
Inside Circle::draw() method. Inside Rectangle::draw() method. Inside Square::draw() method.
工廠模式的優缺點:
優點:一個呼叫者想建立一個物件,只要知道其名稱就可以了。 2、擴充套件性高,如果想增加一個產品,只要擴充套件一個工廠類就可以。 3、遮蔽產品的具體實現,呼叫者只關心產品的介面。
缺點:每次增加一個產品時,都需要增加一個具體類和物件實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的複雜度,同時也增加了系統具體類的依賴。
網友優化:
網友A給出的優化方案:
使用反射機制可以解決每次增加一個產品時,都需要增加一個物件實現工廠的缺點。
public class ShapeFactory { 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; } }
生產物件的時候使用強制轉換
Rectangle rect = (Rectangle) ShapeFactory.getClass(Rectangle.class); rect.draw(); Square square = (Square) ShapeFactory.getClass(Square.class); square.draw();
這樣只需要一個物件實現工廠,不需要每次增加型別時都需要重新寫一個工廠的實現。
網友B對A又進行了進一步優化:
public class ShapeFactory { public static <T> T getClass(Class<? extends T> clazz) { T obj = null; try { obj = (T) Class.forName(clazz.getName()).newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return obj; } }
使用泛型,去掉了每次獲取物件時的強制轉換
Rectangle rect = ShapeFactory.getClass(Rectangle.class); rect.draw(); Shape square = ShapeFactory.getClass(Square.class); square.draw();
網友C又在B的基礎上進一步優化:
針對多個介面實現一個公共的工廠類
public class ObjectFactory { public <T> Object getObject(Class<T> clazz) { if (clazz == null ) { return null; } Object obj = null; try { obj = Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { e.printStackTrace(); } return obj; } }
網友D又在C的基礎上進行了一次優化:
public class ShapeFactory { //使用 getShape 方法獲取形狀型別的物件 public Shape getShape(Class<?> clazz){ try { return (IShape) clazz.getConstructor().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } return null; } }
接下來有位E網友對以上ABCD網友的寫法給出了自己的見解:
其實使用反射是一種不錯的辦法,但反射也是從類名反射而不能從類反射!
先看一下工廠模式是用來幹什麼的——屬於建立模式,解決子類建立問題的。換句話來說,呼叫者並不知道執行時真正的類名,只知道從“Circle"可以創建出一個shape介面的類,至於類的名稱是否叫'Circle",呼叫者並不知情。所以真正的對工廠進行擴充套件的方式(防止程式設計師調用出錯)可以考慮使用一個列舉類(防止傳入引數時,把circle拼寫錯誤)。
如果呼叫者參肯定型別是Circle的話,那麼其工廠沒有存在的意義了!
比如 IShape shape = new Circle();這樣不是更好?也就是說呼叫者有了Circle這個知識是可以直接呼叫的,根據DP(迪米特法則)其實呼叫者並不知道有一個Circle類的存在,他只需要知道這個IShape介面可以計算圓面積,而不需要知道;圓這個類到底是什麼類名——他只知道給定一個”circle"字串的引數,IShape介面可以自動計算圓的面積就可以了!
其實在.net類庫中存在這個模式的的一個典型的。但他引入的另一個概念“可插入程式設計協議”。
那個就是WebRequest req = WebRequest.Create("http://ccc......");可以自動建立一個HttpWebRequest的物件,當然,如果你給定的是一個ftp地址,他會自動建立一個FtpWebRequest物件。工廠模式中著重介紹的是這種通過某個特定的引數,讓你一個介面去幹對應不同的事而已!而不是呼叫者知道了類!
比如如果圓的那個類名叫"CircleShape“呢?不管是反射還是泛型都干擾了你們具體類的生成!其實這個要說明的問題就是這個,呼叫者(clinet)只知道IShape的存在,在建立時給IShape一個引數"Circle",它可以計算圓的面積之類的工作,但是為什麼會執行這些工作,根據迪米特法則,client是不用知道的。
我想問一下那些寫筆記的哥們,如果你們知道了泛型,那麼為什麼不直接使用呢?幹嗎還需要經過工廠這個類呢?不覺得多餘了嗎?
如果,我只是說如果,如果所有從IShape繼承的類都是Internal型別的呢?而client肯定不會與IShape一個空間!這時,你會了現你根本無法拿到這個類名!
Create時使用註冊機制是一種簡單的辦法,比如使用一個列舉類,把功能總結到一處。而反射也是一種最簡單的辦法,呼叫者輸入的名稱恰是類名稱或某種規則時使用,比如呼叫者輸入的是Circle,而類恰是CircleShape,那麼可以通過輸入+”Shape"字串形成新的類名,然後從字串將執行類反射出來!
工廠的建立行為,就這些作用,還被你們用反射或泛型轉嫁給了呼叫者(clinet),那麼,這種情況下,要工廠類何用?!
自認為E的見解是從工廠設計模式的根本出發的,大致可以總結出如下實現:
public enum Factory { CIRCLE(new Circle(),"CIRCLE"), RECTANGLE(new Rectangle(),"RECTANGLE"), SQUARE(new Square(),"SQUARE"); // 成員變數 private Shape shape; private String name; // 普通方法 public static Shape getShape(String name) { for (Factory c : Factory.values()) { if (c.name == name) { return c.shape; } } return null; } // 構造方法 private Factory(Shape shape, String name) { this.shape = shape; this.name = name; } public String getName() { return name; } public Shape getShape() { return shape; } public void setShape(Shape shape) { this.shape = shape; } public void setName(String name) { this.name = name; } }
可使用列舉根據型別來建立想要的物件:
Factory.getShape("CIRCLE").draw(); Factory.getShape("RECTANGLE").draw(); Factory.getShape("SQUARE").draw();