探究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());
}
}
執行結果:
本篇完~
原始碼下週一上傳更新~