工厂模式(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中先启动容器的缘由。