工厂模式

简介

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。工厂模式主要是解决创建对象的问题,典型的应用就是在spring中的IOC,反转控制,反转控制就是把创建对象的权限交给框架,所以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");
    }
}

然后创建工厂类,生成对应的实体类

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中,生成对象的方法不止一种,还可以利用反射机制,工厂接收的是和类相关的参数,可以把这个参数换成需要生成实例的类,这样工厂中生成类的代码就很抽象了。具体代码如下:

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

然后根据提供的配置文件的报名,通过反射实例化对应的类:

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中先启动容器的原因。

posted @ 2018-03-30 18:09  Archieyao  阅读(342)  评论(0编辑  收藏  举报