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.2 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 {
public static String dbType="userMySQLImpl";
/*** 静态工厂方法不可以没有返回值,这里只要不是空的返回值就行******/
public static String initIUserImpl(String dbTypeArg){
MyIUserFactory.dbType=dbTypeArg;
return "success";
}
/*** 获取修改的静态变量然后赋值******/
public static IUser getIUserImpl() {
switch (dbTypeArg) {
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="initIUserImpl">
<!-- 静态构造方法中构造参数 -->
<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
IUser iUser=MyIUserFactory.getIUserImpl();
iUser.insert(new User());
}
}
执行后:
这种方式思路比较奇特,今天偶然想起来的,需要这种场景的可以拿去,不谢。
2.1.3 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());
}
}
执行结果:
本篇完~
源码下周一上传更新~