1. 程式人生 > >探究Java 設計模式之工廠模式

探究Java 設計模式之工廠模式

Java作為一種面向物件的高階語言,程式開發中涉及到很多設計模式。
這篇博文與大家一起探討下工廠模式。

1. 為什麼要用工廠模式?

“Talk is cheap,show me the code”.

想要找到這個問題的答案,我們先來看看下面這個專案。
在這裡插入圖片描述
專案很簡單,一個實體類,一個介面,兩個介面實現類。
實體類 User.java

public class User {
	 public String username;
	 public String password;
}

使用者介面類 IUser.java

import com.xingyun.model.User;
public interface IUser {
	public void insert(User user);
}

使用者介面MySQL實現類IUserMySQLImpl.java

import com.xingyun.interfaces.IUser;
import com.xingyun.model.User;

public class IUserMySQLImpl implements IUser{

	public void insert(User user) {
		// TODO Auto-generated method stub
		System.out.println("insert to MySQL success");
	}
}

Oracle 使用者介面實現類IUserOracleImpl.java

import com.xingyun.interfaces.IUser;
import com.xingyun.model.User;

public class IUserOracleImpl implements IUser{

	public void insert(User user) {
		// TODO Auto-generated method stub
		System.out.println("insert to Oracle success");
	}
}

假設當前有呼叫類Test1.java 想將使用者儲存到MySQL裡,那麼一般會這麼寫

Test1.java

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.model.User;

public class Test1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		IUser iUser=new IUserMySQLImpl();
		
		iUser.insert(new User());
	}
}

輸出結果:
在這裡插入圖片描述
也許你會好奇,為什麼不是

 IUserMySQLImpl  iUser=new IUserMySQLImpl();
iUser.insert(new User());

而是使用下面這樣呢?

IUser iUser=new IUserMySQLImpl();
iUser.insert(new User());

這正是面向介面程式設計的好處,這裡不做詳述,請自行查閱資料瞭解。

Test2.java 和Test1.java 一樣也使用的MySQL的實現類

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.model.User;

public class Test2 {
	public static void main(String[] args) {
		IUser iUser = new IUserMySQLImpl();
		iUser.insert(new User());
	}
}

執行結果如下:
在這裡插入圖片描述
有一天突然通知呼叫者 Test1 和Test2 都要換成Oracle 的實現類,那麼我們將不得不修改Test1.java 和Test2.java 中的程式碼。比如這樣:

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.interfaces.impl.IUserOracleImpl;
import com.xingyun.model.User;

public class Test1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//IUser iUser = new IUserMySQLImpl();
		IUser iUser = new IUserOracleImpl();
		iUser.insert(new User());
	}
}

如果Test1.java Test2.java 比較少還好辦,如果有Test3.java,Test4.java 甚至Test100.java ,這麼多類都改的話就會非常非常累。
那麼有沒有什麼好的辦法呢?
答案是肯定的,它就是我們今天的主角工廠模式。

2 工廠模式實現

2.1 直接編碼實現工廠模式

在這裡插入圖片描述
其他類都不改動,只新增一個新的工廠類,然後改變之前的呼叫方式。
新增一個新類工廠類UserFactory.java

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
public class UserFactory {
	public static IUser getIUserImpl() {
		return new IUserMySQLImpl();
	}
}

然後在Test1.java 和Test2.java 中呼叫的時候就可以這麼寫

import com.xingyun.factory.UserFactory;
import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.interfaces.impl.IUserOracleImpl;
import com.xingyun.model.User;
public class Test1 {
				public static void main(String[] args) {
					// TODO Auto-generated method stub
					IUser iUser = UserFactory.getIUserImpl();
					iUser.insert(new User());
				}
}

在這裡插入圖片描述
Test2 也這麼寫

import com.xingyun.factory.UserFactory;
import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserOracleImpl;
import com.xingyun.model.User;

public class Test2 {
	public static void main(String[] args) {
		IUser iUser = UserFactory.getIUserImpl();
		iUser.insert(new User());
	}
}

執行結果:
在這裡插入圖片描述
有一天突然通知要改用Oracle實現類,那麼我們Test1.java 和Test2.java 不需要做任何更改。只需要更改我們的工廠類就可以了。

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserOracleImpl;
public class UserFactory {
	public static IUser getIUserImpl() {
		//return new IUserMySQLImpl();
		return new IUserOracleImpl();
	}
}

總結:工廠模式通過類的靜態方法返回例項的方式,遮蔽掉了介面具體的實現類,在工廠類中管理統一管理使用哪個實現類。呼叫類Test1.java Test2.java 。。。Test100.java 都不需要做任何程式碼改動,這就是Java傳說中的工廠模式。
需要注意的是,這種方式需要滿足這個需求背景:
當整個專案中要麼全部使用A實現類要麼使用B實現類的時候最適合用。
如果專案中有的用A實現類有的用B實現類,那麼工廠模式就不適合這種場景

2.1 Spring Factory Bean

值得注意的是,如果想用Spring實現工廠模式, Spring並不會直接利用反射機制建立bean物件,而是會利用反射機制先找到Factory類,然後利用Factory再去生成bean物件。
Factory Mothod方式分兩種:

  • 靜態工廠方法
  • 例項工廠方法

2.1.1 Spring 之靜態工廠方法

所謂靜態工廠方式就是指Factory類不本身不需要例項化, 這個Factory類中提供了1個靜態方法來生成bean物件。
在這裡插入圖片描述
正如你看到的,其他類依然不變。
這次是Maven專案,我們需要一些依賴,Spring核心Jar 包和一個日誌包。
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.xingyun</groupId>
	<artifactId>SpringStaticFactoryPatternSample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<description>Spring 通過例項工廠來指定使用哪個介面實現類</description>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>
	</dependencies>
</project>

我們需要修改下工廠類程式碼和新增一個xml 配置檔案。
MyIUserFactory.java

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.interfaces.impl.IUserOracleImpl;

public class MyIUserFactory {

	/***構造方法注入 ******/
	private static IUser getIUserImpl(String dbType) {
		switch (dbType) {
		case "userMySQLImpl":
			return new IUserMySQLImpl();
		case "userOracleImpl":
			return new IUserOracleImpl();
		default:
			return new IUserMySQLImpl();
		}
	}
}

配置檔案裡面我們需要改變下:
beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 通過靜態工廠方法 -->
<!-- bean id  class="工廠類"  factory-method="工廠方法" -->
<bean id="iUser" class="com.xingyun.factory.MyIUserFactory" factory-method="getIUserImpl">
   <!-- 靜態構造方法中構造引數  -->
   <constructor-arg value="userMySQLImpl"></constructor-arg>
   <!-- default is userMySQLImpl,optional:userMySQLImpl,userOracleImpl -->
</bean>     
</beans>

以後需求一旦變更,Test1.java 和Test2.java 不用改變,我們只需要修改這個配置檔案即可。如果做了配置會根據配置指定到底使用哪個實現類,如果不配置預設會使用MySQL實現類。
將MySQL介面實現類切換到Oracle介面實現類就這麼做,

 <constructor-arg value="userMySQLImpl"></constructor-arg>

改成如下即可

 <constructor-arg value="userOracleImpl"></constructor-arg>

呼叫方式也需要做下改變:
Test1.java

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import com.xingyun.interfaces.IUser;
import com.xingyun.model.User;

public class Test1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
        Resource resource=new FileSystemResource("src/main/resources/beans.xml");
		
		BeanFactory beanFactory=new XmlBeanFactory(resource);
		
		IUser iUser=(IUser)beanFactory.getBean("iUser");
		
		iUser.insert(new User());
	}
}

執行後:
在這裡插入圖片描述

2.1.1 Spring 之例項工廠模式

在這裡插入圖片描述
和剛才略有不同,工廠類做了改動,xml配置檔案做了改動。其他都不用動。
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.xingyun</groupId>
	<artifactId>SpringStaticFactoryPatternSample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<description>Spring 靜態工廠方法</description>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>
	</dependencies>
</project>

MyIUserFactory.java

import com.xingyun.interfaces.IUser;
import com.xingyun.interfaces.impl.IUserMySQLImpl;
import com.xingyun.interfaces.impl.IUserOracleImpl;

public class MyIUserFactory {

	private IUser getIUserImpl(String dbType) {
		switch (dbType) {
		case "userMySQLImpl":
			return new IUserMySQLImpl();
		case "userOracleImpl":
			return new IUserOracleImpl();
		default:
			return new IUserMySQLImpl();
		}
	}
}

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="iUserFactory" class="com.xingyun.factory.MyIUserFactory" >
	</bean>
	<!-- bean id class="工廠類" factory-method="工廠方法" -->
	<bean id="iUser" factory-bean="iUserFactory" factory-method="getIUserImpl">
		<constructor-arg value="userMySQLImpl" /> 
		<!-- default is userMySQLImpl,optional:userMySQLImpl,userOracleImpl -->
	</bean>
</beans>

今後一旦需求改動,只需要改配置檔案,其他全部不用改了。

2.1 Spring Bean Factory

Spring 的IOC容器提供了一個最強大的Bean 工廠,因此如果針對上述需求,你需要不同調用類呼叫不同的實現的話,那麼也可以這麼做。
在這裡插入圖片描述
其他類都不變,只修改beans.xml 和呼叫方式
pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.xingyun</groupId>
	<artifactId>SpringBeanFactoryPatternSample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<description>Spring Bean工廠 案例原始碼</description>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- Spring 核心Jar包 -->
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>5.1.2.RELEASE</version>
		</dependency>
	</dependencies>
</project>

Spring3.1以後XmlBeanFactory已經廢棄,即下面這段程式碼已經廢棄
Resource resource = new ClassPathResource(“applicationContext.xml”); //裝載配置檔案
BeanFactory factory = new XmlBeanFactory(resource);

新的替代呼叫方式有兩種:
第一種:當呼叫getBean()的時候才會配置檔案中的bean才會例項化

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import com.xingyun.interfaces.IUser;
import com.xingyun.model.User;

public class Test1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
        Resource resource=new FileSystemResource("src/main/resources/beans.xml");
        BeanFactory fa=new DefaultListableBeanFactory();  
		BeanDefinitionReader bdr=new XmlBeanDefinitionReader((BeanDefinitionRegistry) fa);  
		bdr.loadBeanDefinitions(resource);
		
		IUser iUser=(IUser)fa.getBean("iUserMySQLImpl");
		
		iUser.insert(new User());
		
	}
}

執行結果:
在這裡插入圖片描述
第二種:讀取XMl配置檔案的時候配置檔案中所有的bean 就全部例項化

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xingyun.interfaces.IUser;
import com.xingyun.model.User;

public class Test2 {
	public static void main(String[] args) {
		
		ApplicationContext fa=new ClassPathXmlApplicationContext("beans.xml");
			
		IUser iUser=(IUser)fa.getBean("iUserOracleImpl");
			
		iUser.insert(new User());
	}
}

執行結果:
在這裡插入圖片描述
本篇完~
原始碼下週一上傳更新~