java設計模式04.1Factory-----對象創建模式(Factory Method、Abstract Factory、Prototype、Builder)

前言

爲什麼有這種模式,爲的是繞過常規的對象創建方法(new),提供一種“封裝機制”來避免客戶程序和這種“具體對象創建工作”的緊耦合。
定義一個用於創建對象的接口,讓子類決定實例化哪一個類。Factory Method 使得一個類的實例化延遲(目的:解耦,手段:虛函數)到子類。

結構圖

在這裏插入圖片描述

具體實例(來源:菜鳥教程)簡單工廠

這是一個簡單工廠

//定義Product接口
public interface Shape {
   void draw();
}
//具體實現Product
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.");
   }
}

//工廠用於對象的創建
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;
   }
}

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();
   }
}

這裏需要說明的是菜鳥教程提供的工廠模式更準確的說,這應該是簡單工廠模式,簡單工廠模式不屬於23種設計模式中的一種。
工廠模式與簡單工廠模式差別就在與對工廠的抽象,也就是結構圖中的creater。

存在的困惑

如果我現在加了個菱形這樣一個類,必然要對工廠(shapeFactory)裏面的if else作出修改,這違反了開閉原則。於是我看見有網友提出採用反射,我一度的以爲這種方法是正確的,其實不然。
先看一下采取反射的寫法:

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();

這樣確實,我新加的類不用去修改shapeFactory工廠類了,一定程度上滿足了開閉原則。


這種方式有一個很大的弊端就是違反了迪米特法則(最少知道原則),也違反了工廠模式的初衷,工廠模式的存在就是爲了讓用戶不用知道詳細的實現細節,用戶只需要知道,他需要一個什麼就行了。然而用反射,因爲反射是從類名反射而不能從類反射,也就是說用戶要知道類名。這裏感謝菜鳥教程的網友kusirp21。
至於使用枚舉對工廠模式優化,暫時不做評價,等以後我的水平有所提升再做討論,

工廠模式

public interface Loggerpublic void writeLog();public class DatabaseLogger implements Logger{
	public void writeLog(){
		System.out.println("數據庫日誌記錄");
	}
}
public class FileLogger implements Logger{
	public void writeLog(){
		System.out.println("文件日誌記錄");
	}
}

//工廠接口
public interface LoggerFactory{
	public Logger createLogger();
}
public class DatabaseLoggerFactory implements LoggerFactory{
	public Logger createLogger(){
		Logger logger = new DatabaseLogger();
		return logger;
	}
}
public class FileLoggerFactory implements LoggerFactory{
	public Logger createLogger(){
		Logger logger = new FileLogger();
		return logger;
	}
}

//Client客戶端測試類
public class Client{
	public static void main(String args[]){
		LoggerFactory factory;
		Logger logger;
		factory = new FileLoggerFactory();
		logger =  factory.createLogger();
		logger.writeLog();
	}
}

如果需要增加並使用新的日誌記錄器,只需要對應增加一個新的具體工廠類,然後在客戶端代碼中修改具體工廠類名,原有類庫的源代碼無須做任何修改。
java設計模式(劉偉)一書提到,通過引入配置文件並使用反射機制可以實現不修改客戶端代碼的基礎上更換具體工廠類,使其更加符合開閉原則,具有更好的靈活性和可擴展性。

反射機制與配置文件

在實際開發中可以對具體工廠類的實例化過程進行改進,在客戶端代碼中不直接使用new關鍵字來創建工廠對象,而是通過Java反射機制結合配置文件(例如XML文件)來生成具體工廠對象。在整個實現過程中用到兩個技術,即Java反射機制與配置文件。

1.java反射機制

java反射:JAVA反射機制是在運行狀態中,對於任意一個實體類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。又可以說爲動態創建。

//通過類名生成實例對象並將其返回
Class c = Class.forName("java.lang.String");
Object obj = c.newInstance();
return obj;
2.配置文件

類名可以存儲到XML配置文件中,再讀取配置文件獲取類名字符串,然後通過java反射機制來創建對象。以上面的FileLoggerFactory爲例

<!-- config.xml -->
<?xml version = "1.0"?>
<config>
	<className>類路徑(包名.類名)</className>
</config>

爲了讀取該配置文件,並通過存儲在其中的類名,字符串,反射生成對象,可以創建一個工具類XMLUtil,其詳細代碼如下:

import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;

public class XMLUtil{
	//該方法用於從XML配置文件中提取具體類的類名,並返回一個實例對象
	public static Object getBean(){
		try{
			//創建DOM文檔對象
			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = dFactory.newDocumentBuilder();
			Document doc;
			doc = builder.parse(new File("src//designpatterns//factorymethod//config.xml"));


			//獲取包含類名的文本節點
			NodeList nl=doc.getElementsByTagName("className");
			Node classNode = nl.item(0).getFirstChild();
			String cName=classNode.getNodeValue();
			
			//通過類名生成實例對象並返回
			Class c=Class.forName(cName);
			Object obj=c.newInstance();
			return obj;
		}
	catch(Exception e){
			e.printStackTrace();
			return null; 
		}	
	}
}

客戶端類:

public class Client{
	public static void main(String args[]){
		LoggerFactory factory;
		Logger logger;
		factory=(LoggerFactory)XMLUtil.getBean();
		logger=factory.createLogger();
		logger.writeLog();
	}
}

總結:一句話概括工廠模式

簡單工廠:一個工廠類,一個產品抽象類。
工廠方法:多個工廠類,一個產品抽象類。
抽象工廠:多個工廠類,多個產品抽象類。

參考文獻

[1]https://www.runoob.com/design-pattern/factory-pattern.html
[2]Java設計模式 作者:劉偉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章