《Design Patterns》AbstractFactory.積跬步系列

AbstractFactory:抽象工廠模式

先代碼

該文章代碼主要分兩個版本:分別是基於抽象工廠模式原理實現的基礎版本及在基礎版上使用簡單工廠模式+反射技術實現的改進版本。
基於抽象工廠模式原理實現的基礎版本:

package h.l.demo.abstractfactory.explain;
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月28日
 * @Description: Dept接口
 */
public abstract class IDept {
	/**
	 * 插入一個用戶
	 * 
	 * @param userId
	 *            用戶id
	 * @param username
	 *            用戶名
	 */
	public abstract void insertDept(String deptId, String deptName);

	/**
	 * 刪除一個用戶
	 * 
	 * @param userId
	 *            用戶id
	 */
	public abstract void deleteDept(String deptId);
}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 模擬mysql數據庫操作Dept類
 */
class MysqlDept extends IDept {

	@Override
	public void insertDept(String deptId, String deptName) {
		System.out.println("向MysqlDept插入部門數據:" + deptId + "-" + deptName);
	}

	@Override
	public void deleteDept(String deptId) {
		System.out.println("向MysqlDept刪除部門數據:" + deptId);
	}

}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 模擬sqlserver數據庫操作Dept類
 */
class SqlserverDept extends IDept {

	@Override
	public void insertDept(String deptId, String deptName) {
		System.out.println("向SqlserverDept插入部門數據:" + deptId + "-" + deptName);
	}

	@Override
	public void deleteDept(String deptId) {
		System.out.println("向SqlserverDept刪除部門數據:" + deptId);
	}

}
package h.l.demo.abstractfactory.explain;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: User接口
 */
public interface IUser {
	/**
	 * 插入一個用戶
	 * @param userId 用戶id
	 * @param username 用戶名
	 */
	public void insertUser(String userId, String username);
	/**
	 * 刪除一個用戶
	 * @param userId 用戶id
	 */
	public void deleteUser(String userId);
}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 模擬mysql數據庫操作User類
 */
class MysqlUser implements IUser {

	@Override
	public void insertUser(String userId, String username) {
		System.out.println("向MysqlUser插入用戶數據:" + userId + "-" + username);
	}

	@Override
	public void deleteUser(String userId) {
		System.out.println("向MysqlUser刪除用戶數據:" + userId);
	}

}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 模擬sqlserver數據庫操作User類
 */
class SqlserverUser implements IUser {

	@Override
	public void insertUser(String userId, String username) {
		System.out.println("向SqlserverUser插入用戶數據:" + userId + "-" + username);
	}

	@Override
	public void deleteUser(String userId) {
		System.out.println("向SqlserverUser刪除用戶數據:" + userId);
	}

}

抽象工廠類:

package h.l.demo.abstractfactory.explain;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 抽象工廠/接口
 */
public abstract class IFactory {

	/**
	 * 創建IUser實例
	 * @return
	 */
	public abstract IUser createUser();
	/**
	 * 創建IDept實例
	 * @return
	 */
	public abstract IDept createDept();
}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: mysql數據庫操作工廠
 */
class MysqlFactory extends IFactory{

	@Override
	public IUser createUser() {
		System.out.println("MysqlFactory 創建IUser實例:"+new SimpleDateFormat().format(new Date()));
		return new MysqlUser();
	}

	@Override
	public IDept createDept() {
		System.out.println("MysqlFactory 創建IDept實例:"+new SimpleDateFormat().format(new Date()));
		return new MysqlDept();
	}
	
}
/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: sqlserver數據庫操作工廠
 */
class SqlserverFactory extends IFactory{

	@Override
	public IUser createUser() {
		System.out.println("SqlserverFactory 創建IUser實例:"+new SimpleDateFormat().format(new Date()));
		return new SqlserverUser();
	}

	@Override
	public IDept createDept() {
		System.out.println("SqlserverFactory 創建IDept實例:"+new SimpleDateFormat().format(new Date()));
		return new SqlserverDept();
	}
	
}

測試類:

package h.l.demo.abstractfactory.explain;

import java.util.UUID;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月14日
 * @Description: 測試
 */
public class TestMainEnter {

	public static void main(String[] args) {
		// 使用mysql
		IFactory factory = new MysqlFactory();
		IUser user = factory.createUser();
		user.insertUser("userid", "helloWorld");
		user.deleteUser("userid");
		IDept dept = factory.createDept();
		dept.insertDept("deptid", "helloJava");
		dept.deleteDept("deptid");
		System.out.println("-------------------------------");
		// 使用SqlServer數據庫  和上面mysql使用的代碼就只有factorynew的對象換一下即可
		factory = new SqlserverFactory();
		user = factory.createUser();
		user.insertUser("userid", "helloWorld");
		user.deleteUser("userid");
		dept = factory.createDept();
		dept.insertDept("deptid", "helloJava");
		dept.deleteDept("deptid");
	}

}

測試結果:
在這裏插入圖片描述


基礎版上使用簡單工廠模式+反射技術實現的改進版本:
主要是使用簡單工廠模式處理上一個版本IFactory.java中諸多類的關係,改進版只需要將IFactory替換爲下面這個DataAccess.java即可,同時配一個properties屬性文件,便於在不重新編譯的情況下可以修改db屬性。其餘如IUser.java、IDept.java和上一版本保持不變。具體代碼如下:

package h.l.demo.abstractfactory.explain_improved_version;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月27日
 * @Description: 使用簡單工廠取代IFactory、MysqlFactory、SqlserverFactory三個類
 */
public class DataAccess {

	/**
	 * 根據數據庫返回對應的數據庫User操作類
	 * 
	 * @param db
	 *            數據庫
	 * @return
	 */
	/*
	 * public static IUser createUser(String db) { IUser result = null; switch
	 * (db) { case "Mysql": result = new MysqlUser(); break; case "Sqlserver":
	 * result = new SqlserverUser(); break; } return result; }
	 *//**
	 * 根據數據庫返回對應的數據庫Dept操作類
	 * 
	 * @param db
	 *            數據庫
	 * @return
	 */
	/*
	 * public static IDept createDept(String db) { IDept result = null; switch
	 * (db) { case "Mysql": result = new MysqlDept(); break; case "Sqlserver":
	 * result = new SqlserverDept(); break; } return result; }
	 */

	// 上述代碼是沒有使用反射的代碼,僅僅是簡單工廠的應用,對於諸如pgsql新的數據庫加入還得
	// 修改該簡單工廠,所以現在使用反射來解決

	private static String dbClassName;
	static {
		// 加載指定的數據庫操作類,類路徑,如:
		// h.l.demo.abstractfactory.explain_improved_version.Mysql+User
		Properties properties = new Properties();
		try {
			// 訪問的src同級目錄下的properties寫法
			// properties.load(new FileInputStream(new File("./dbConfig.properties")));
			// 訪問相對於當前類來說的properties寫法
			String path = DataAccess.class.getResource("dbConfig.properties").getPath();
			System.out.println(path);
			properties.load(new FileInputStream(new File(path)));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		dbClassName = properties.getProperty("dbClassName");
		System.out.println(dbClassName);
	}

	/**
	 * 生產指定的數據庫User操作類實例
	 * 
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	public static IUser createUser()
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException {
		Class<?> clazz = Class.forName(dbClassName + "User");
		return (IUser) clazz.newInstance();
	}
	/**
	 * 生產指定的數據庫Dept操作類實例
	 * 
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	public static IDept createDept()
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException {
		Class<?> clazz = Class.forName(dbClassName + "Dept");
		return (IDept) clazz.newInstance();
	}
}

dbConfig.properties文件:

dbClassName=h.l.demo.abstractfactory.explain_improved_version.Sqlserver
#dbClassName=h.l.demo.abstractfactory.explain_improved_version.Mysql

測試類:

package h.l.demo.abstractfactory.explain_improved_version;

import java.util.UUID;

/**
 * 
 * @author: Is-Me-Hl
 * @date: 2020年2月14日
 * @Description: 測試
 */
public class TestMainEnter {

	public static void main(String[] args) {
		try {
			IUser user = DataAccess.createUser();
			user.insertUser("userid", "Are you ok?");
			user.deleteUser("userid");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

測試結果:
在這裏插入圖片描述

後分析

  • 個人建議:寫代碼是件幸福的事,So,do it

抽象工廠模式,定義是提供一個創建一系列相關或相互依賴對象的接口,而無需指定他們的具體類。實現即如上面代碼中的第一版代碼。最大的好處就是易於交互產品系列,只需要改變具體的工廠就可以使用不同的產品配置,如版本一代碼中使用的兩種數據可Mysql及Sqlserver的配置類。其次就是能讓具體的創建實例過程和客戶端分離,(實際上這和簡單工廠的意圖有些類似,所以在第二版改進第一版時使用了簡單工廠模式來改進了,減少了代碼量),客戶端通過他們的抽象接口操縱實例,產品的具體類名也被抽象工廠分離,不會出現在客戶端中。事實上,上述代碼中,客戶端所認識的只有IUser、IDept,至於底層用的Sqlserver還是Mysql實現的,那就不知道了。之所以上述改進版代碼中使用了反射,實際上也是爲了可擴展,遵循開閉原則,也就是說理論上所有在用簡單工廠的地方,都可以考慮用反射技術去除switch、if,解除分支判斷帶來的耦合。

對比工廠方法模式:其定義:定義一個用於創建對象的接口,讓子類決定實例化哪一個類。從兩者定義上可以看出區別:都是有那麼一個接口,一個需要的是這個接口能創建一系列相關或依賴對象,一個是創建對象。數量上的不同。所以可以看做工廠方法是抽象工廠的一個特列。
要說兩者的區別還有什麼,那就是在理解上,抽象工廠就相當於一個一個公司,旗下有很多產品,產品A,產品B…產品N都是該公司生產的。工廠方法,就相當於於一個產品對應一個組裝工廠,該工廠比如能組裝手機,那麼生產出來的可能是A公司手機,B公司手機。兩者維度不一樣,一個是統一公司下的產品,另一個是產品來自不同的公司,他們都可以生產。網上看了一個很有意思的舉例:

手機有小米手機、華爲手機,它們都是手機,這些具體的手機和抽象手機就構成了一個產品等級結構。同樣的,路由器有小米路由器,華爲路由器,這些具體的路由器和抽象路由器就構成了另外一個產品等級結構,實質上產品等級結構即產品的繼承結構。小米手機位於手機產品等級結構中,小米路由器位於路由器的產品等級結構中,而小米手機和小米路由器都是小米公司生產的,就構成了一個產品族,同理,華爲手機和華爲路由器也構成了一個產品族 。劃重點就是產品族中的產品都是由同一個工廠生產的,位於不同的產品等級結構
在這裏插入圖片描述
對比工廠方法,其針對的是產品等級結構,而抽象工廠是針對產品族。在二者的使用選擇上,需要結合實際業務,對於產品等級數量相對固定的產品族,可以優先考慮抽象工廠模式,但是如果頻繁變動,則不大適用,因爲在現有的產品族中新增產品等級時,就需要修改產品族工廠,也就違背了開閉原則。

其他例子:參考自《大話設計模式》就不能不換DB嗎?


注:以上文章僅是個人總結,若有不當之處,望不吝賜教

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