工廠模式(Factory Pattern)是 Java 中最經常使用的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。工廠模式主要是解決建立對象的問題,典型的應用就是在spring中的IOC,反轉控制,反轉控制就是把建立對象的權限交給框架,因此spring就是一個生產對象的工廠。java
工廠模式的思路就是設計一個產生對象的機制,讓生產對象的過程交給第三方,在工廠模式中,不會對客戶端暴露建立邏輯,而且使用通用接口接收新建立的對象。spring
這種方式是最簡單的實現方式:設計模式
// 建立接口 public interface Shape { void draw(); } // 建立實體類Circle public class Circle implements Shape { @Override public void draw() { System.out.println("drawing a circle"); } } // 建立實體類Rectangle public class Rectangle implements Shape { @Override public void draw() { System.out.println("drawing a Rectangle"); } } // 建立實體類Square public class Square implements Shape { @Override public void draw() { System.out.println("drawing a square"); } }
而後建立工廠類,生成對應的實體類app
public class ShapeFactory { public static Shape getShapes(String shapeType) { if (shapeType == null) { System.out.println("shapeType is null"); throw new RuntimeException(); } else if (shapeType.equalsIgnoreCase("Rectangle")) { return new Rectangle(); } else if (shapeType.equalsIgnoreCase("Square")) { return new Square(); }else if(shapeType.equalsIgnoreCase("Circle")){ return new Circle(); }else { System.out.println("nothing to do"); return null; } } // 測試簡單工廠模式 @Test public void testSimpleFactoryPattern(){ Shape circle = ShapeFactory.getShapes("circle"); circle.draw(); Shape rectangle = ShapeFactory.getShapes("Rectangle"); rectangle.draw(); Shape square = ShapeFactory.getShapes("Square"); square.draw(); } }
這種方式實現工廠模式很簡單,可是缺點也很明顯,好比,在增長一個實現Shape接口的實體類,又須要去修改ShapeFactory中的代碼,這樣其實不符合設計模式的原則,對擴展開放,對修改關閉。框架
分析一下,之因此每新增一個類都須要去修改工廠的代碼,是由於在工廠中,生成類的代碼太具體了,要想改變這種狀況,就須要把這個工廠生成類實例的過程變得抽象化,在Java中,生成對象的方法不止一種,還能夠利用反射機制,工廠接收的是和類相關的參數,能夠把這個參數換成須要生成實例的類,這樣工廠中生成類的代碼就很抽象了。具體代碼以下:ide
public class ShapeFactory { public static Shape getClass(Class<? extends Shape> clazz) { Shape shape = null; try { // 經過反射生成一個類的實例 shape = (Shape) Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return shape; } // 測試反射改進後的工廠 @Test public void testReflectShapeFac(){ Shape rectangle = ShapeFactory.getClass(Rectangle.class); rectangle.draw(); Shape square = ShapeFactory.getClass(Square.class); square.draw(); Shape circle = ShapeFactory.getClass(Circle.class); circle.draw(); } }
這樣工廠的生產過程就很抽象了,可是還有一個問題,這個工廠只能生成實現Shape接口的類實例,若是出現了另一種接口,就有須要新增一個工廠,這樣也何嘗不可,由於只是擴展而已,可是又出現了一個新的問題,這些工廠中的生產對象的代碼都差很少,只是強轉的接口不一樣,代碼仍是有優化的空間的。工廠能夠進一步抽象:測試
// 改進後的工廠方法 public static <T> T getClass(Class<? extends T> clazz){ T obj = null; try { obj= (T) Class.forName(clazz.getName()).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return obj; }
到這裏,能夠去思考一下在spring中,是怎麼利用工廠模式的,首先你須要去xml中配置你須要實例化的類,而後讀取這個配置文件,經過反射生成這個類的實例返回。其實這裏也能夠簡單模仿一下:優化
// 解析Properties配置文件 public class PropertiesUtil { private static Properties properties = new Properties(); public static String getPackageByName(String name) { return properties.getProperty(name); } // 解析配置文件 private static Map<String, String> parseProperties() { Map<String, String> map = new HashMap<>(); InputStream inputStream = PropertiesUtil.class.getClassLoader().getResourceAsStream("application.properties"); try { properties.load(inputStream); } catch (IOException e) { System.err.println("file is not exists"); e.printStackTrace(); } return map; } }
配置文件中的內容以下:設計
# 類名和包名的映射 circle=com.factory.pattern.simple.Circle rectangle=com.factory.pattern.simple.Rectangle square=com.factory.pattern.simple.Square
而後根據提供的配置文件的報名,經過反射實例化對應的類:code
public class ConfigFactory { // 經過配置文件中的包名生成實例 public static <T> T getNewInstance(String className) { String packageName = PropertiesUtil.getPackageByName(className); try { return (T) Class.forName(packageName).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } }
這種方式須要須要注意的是,加載配置文件必定是在調用工廠的前面,由於須要讀取報名,把對應的數據讀到內存中,這也就是spring中先啓動容器的緣由。