工厂模式 {#工厂模式}
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
介绍 {#介绍}
**意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
**主要解决:**主要解决接口选择的问题。
**何时使用:**我们明确地计划不同条件下创建不同实例时。
**如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。
**关键代码:**创建过程在其子类执行。
应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
**缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
**注意事项:**作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
示范代码 {#示范代码}
由于客户的数据库环境经常变化,希望更换数据库时候,只需要修改 dao 的代码,不需要修改 service。
- 设计 dao
public interface UserDao {
public void save();
}
- 设计 daoimpl 实现
/**
基于 MySQL 存储实现类
*/
public class UserDaoMysqlImpl implements UserDao {
@Override
public void save() {
System.out.println("数据已经保存到 MySQL 数据库中了");
}
}
- 设计 service 接口
public interface UserService {
public void save();
}
- 设计 serviceimpl 实现
public class UserServiceImpl implements UserService {
// 注入 Dao
private UserDao userDao = new UserDaoMysqlImpl();
<span class="token comment">/**
* @Author: Auser·杰
* Development: IntelliJ IDEA 2018.2.4 x64
* MethodName: save
* Param: []
* Return: void
* 方法说明: 调用 dao 方法
*/</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
userDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
- 测试类
public class UserTest {
@Test
public void testSave() {
// New 一个实现方法
UserService userService = new UserServiceImpl();
userService.save();
}
}
- 开闭原则
在一个项目中, 如果需要添加功能, 最好是遵守开闭原则
- 对修改关闭(不能修改代码)
- 对扩展开放
比如在一个项目中, 如果你需要添加功能, 最好是在项目已有的基础上进行添加模块(添加类, 接口, 方法), 简单说就是尽量少改代码, 而是扩展代码
优化代码 {#优化代码}
- 这时,需要从 mysql 切换到 Oracle 数据,根据开闭原则,扩展了 Dao 实现
public class UserDaoOracleImpl implements UserDao {
@Override
public void save() {
System.out.println("数据已经保存到 Oracle 数据库中了");
}
}
- 切换 dao 实现类
public class UserServiceImpl implements UserService {
// 注入 Dao
// private UserDao userDao = new UserDaoMysqlImpl();
<span class="token comment">// 切换 Dao 实现类</span>
<span class="token keyword">private</span> <span class="token class-name">UserDao</span> userDao <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">UserDaoOracleImpl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/**
* 方法说明: 调用 dao 方法
*/</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
userDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
这样每次都需要修改代码,Service 和 Dao 的耦合性太高
可以使用工厂模式 (Factory) 解决问题
- 可以设计一个对象工厂
/**
创建对象(UserDao 实现类对象)
*/
public class BeanFactory {
public static UserDao getBean() {
return new UserDaoMysqlImpl();
}
}
- 业务层就可以从工厂获取对象
public class UserServiceImpl implements UserService {
// 注入 Dao
// private UserDao userDao = new UserDaoMysqlImpl();
<span class="token comment">// 切换 Dao 实现类</span>
<span class="token comment">// private UserDao userDao = new UserDaoOracleImpl();</span>
<span class="token comment">// 从工厂获取对象</span>
<span class="token keyword">private</span> <span class="token class-name">UserDao</span> userDao <span class="token operator">=</span> <span class="token class-name">BeanFactory</span><span class="token punctuation">.</span><span class="token function">getBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/**
* 方法说明: 调用 dao 方法
*/</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
userDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
这个时候如果我们需要更换需求(切换数据库), 可以直接修改工厂类里面的代码
/**
创建对象(UserDao 实现类对象)
*/
public class BeanFactory {
public static UserDao getBean() {
return new UserDaoOracleImpl();
}
}
但是这个工厂不好, 功能太少, 我们需要构建一个可以创建任何对象的工厂
- 在 resources 下面创建一个
bean.properties
userDao=com.mobaijun.dao.impl.UserDaoMysqlImpl
userService=com.mobaijun.service.impl.UserServiceImpl
- 优化工厂
// 实例化一个 bundle
private static ResourceBundle bundle;
<span class="token comment">// 通过读取 bean.properties 创建相应的对象</span>
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Object</span> <span class="token function">getBean</span><span class="token punctuation">(</span><span class="token class-name">String</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// 读取 bean.properties</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>bundle <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
bundle <span class="token operator">=</span> <span class="token class-name">ResourceBundle</span><span class="token punctuation">.</span><span class="token function">getBundle</span><span class="token punctuation">(</span><span class="token string">"bean"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 通过 key 获取 value</span>
<span class="token class-name">String</span> className <span class="token operator">=</span> bundle<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 通过反射创建对象</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token class-name">Class</span><span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span>className<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
* 修改 service
```java
public class UserServiceImpl implements UserService {
// 注入 Dao
// private UserDao userDao = new UserDaoMysqlImpl();
<span class="token comment">// 切换 Dao 实现类</span>
<span class="token comment">// private UserDao userDao = new UserDaoOracleImpl();</span>
<span class="token comment">// 从工厂获取对象</span>
<span class="token keyword">private</span> <span class="token class-name">UserDao</span> userDao <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">UserDao</span><span class="token punctuation">)</span> <span class="token class-name">BeanFactory</span><span class="token punctuation">.</span><span class="token function">getBean</span><span class="token punctuation">(</span><span class="token string">"userDao"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">/**
* 方法说明: 调用 dao 方法
*/</span>
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
userDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
```
<br />
* 修改测试
```Java
public class UserTest {
@Test
public void testSave() {
// New 一个实现方法
UserService userService = (UserService) BeanFactory.getBean("userService");
userService.save();
}
}
```
> 这个时候所有的依赖关系全部转移到了配置文件`bean.properties`, 需要更换数据库需求时, 直接修改配置文件即可, 这个时候代码的耦合度就降低了(service/dao 解耦), 但是耦合没有消失, 只是转移到配置文件了
>
>
>
>
> * 使用工厂模式, 可以让 service 和 Dao 的耦合降低
>
>
>
* 修改配置文件
```properties
# userDao=com.mobaijun.dao.impl.UserDaoMysqlImpl
userDao=com.mobaijun.dao.impl.UserDaoOracleImpl
userService=com.mobaijun.service.impl.UserServiceImpl
```
> 可以称 IOC 容器为 Bean 工厂, 提供解耦,(工厂就是 IOC 容器)
>
>
> 未经授权, 禁止转载;
>
>
> > 本文页头内容参考菜鸟教程:https://www.runoob.com/design-pattern/factory-pattern.html
> >
> >
>
>