工廠模式

簡介

工廠模式(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中先啓動容器的緣由。

相關文章
相關標籤/搜索