应用场景
从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法模式,但不属于23中GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单使用的模式,可以理解为是不同工厂模式的一个特殊实现。
简单工厂模式实例

可以看出,上面有一个产品接口IProduct,一个工厂类Creator,两个产品类A,B。工厂类负责整个创建产品的逻辑判断,所以为了能使工厂类能够知道我们需要哪一种产品,我们需要在创建产品时传递给工厂类一个参数,去表明我们想要创建哪种产品。
接下来敲代码,呜呜。
产品接口IProduct1
2
3
4public interface IProduct {
public void method();
}
两个产品类1
2
3
4
5
6
7public class ProductA implements IProduct{
public void method() {
System.out.println("产品A方法");
}
}
1 | public class ProductB implements IProduct{ |
最后一个是工厂类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Creator {
private Creator(){}
public static IProduct createProduct(String productName){
if (productName == null) {
return null;
}
if (productName.equals("A")) {
return new ProductA();
}else if (productName.equals("B")) {
return new ProductB();
}else {
return null;
}
}
}
然后我们来看看怎么调用的1
2
3
4
5
6
7
8
9
10public class Client {
public static void main(String[] args) {
IProduct product1 = Creator.createProduct("A");
product1.method();
IProduct product2 = Creator.createProduct("B");
product2.method();
}
}
最后会输出1
2产品A方法
产品B方法
这个代码太简单了,应该都看的懂吧~ 两个产品实现的同一个接口,然后就可以造产品了,就像有两个不同的产品都在一个工厂,你会去让另一个工厂去造A,再让另一个工厂去造B吗?显然不会呀,我肯定让这一个工厂造,就这意思。
简单工厂实战
就拿简单的WEB项目来做例子吧。
都开发过SSH项目吧!
众所周知,我们平时开发web项目大部分是以spring作为平台,来集成各个组件,比如集成struts2来完成业务层与表现层的逻辑,集成hibernate或者ibatis来完成持久层的逻辑。
struts2在这个过程当中提供了分离数据持久层,业务逻辑层以及表现层的责任,有了Struts2,我们不再需要servlet,而是可以将一个普通的Action类作为处理业务逻辑的单元,然后将表现层交给特定的视图去处理,比如JSP,template等等。
先把WEB项目中需要的类都列出来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import javax.servlet.http.HttpServlet;
//假设这是一个小型的WEB项目,我们通常里面会有这些类
//这个类在代理模式出现过,是我们的数据源连接池,用来生产数据库连接。
class DataSource{}
//我们一般会有这样一个数据访问的基类,这个类要依赖于数据源
class BaseDao{}
//一般会有一系列这样的DAO去继承BaseDao,这一系列的DAO类便是数据持久层
class UserDao extends BaseDao{}
class PersonDao extends BaseDao{}
class EmployeeDao extends BaseDao{}
//我们还会有一系列这样的servlet,他们通常依赖于各个Dao类,这一系列servlet便是我们的业务层
class LoginServlet extends HttpServlet{}
class LoginOutServlet extends HttpServlet{}
class RegisterServlet extends HttpServlet{}
//我们通常还会有HTML页面或者JSP页面,但是这个本次不在考虑范围内,这便是表示层。
我们的servlet一般都是继承自HttpServlet,因为我们在web.xml配置servlet时,所写入的Class需要实现servlet接口,而我们通常采用的传输协议都是HTTP,所以HttpServlet就是我们最好的选择了,它帮我们完成了基本的实现。
这三个servlet的功能分别是进行登录,注销,以及注册新用户的功能。
但是如果我们让一个Servlet负责多种业务逻辑的话,那我们需要在doPost方法中加入很多if判断,去判断当前的操作。1
2
3
4
5
6
7
8
9
10
11
12
13protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//我们加入一个操作的参数,来让servlet做出不同的业务处理
String operation = req.getParameter("operation");
if (operation.equals("login")) {
System.out.println("login");
}else if (operation.equals("register")) {
System.out.println("register");
}else if (operation.equals("loginout")) {
System.out.println("loginout");
}else {
throw new RuntimeException("invalid operation");
}
}
这种方法并不是很好,因为每次添加一个操作,都要修改doPost这个方法,而且多个业务逻辑都集中在这个一个方法中,会让代码很难维护与扩展,最容易想到的就是下列做法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//我们加入一个操作的参数,来让servlet做出不同的业务处理
String operation = req.getParameter("operation");
if (operation.equals("login")) {
login();
}else if (operation.equals("register")) {
register();
}else if (operation.equals("loginout")) {
loginout();
}else {
throw new RuntimeException("invalid operation");
}
}
private void login(){
System.out.println("login");
}
private void register(){
System.out.println("register");
}
private void loginout(){
System.out.println("loginout");
}
这也很烂,我们继续优化。
我们看一下Struts2怎么做到的。1
2
3
4
5
6
7
8
9<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这是struts2最核心的filter,它的任务就是分派各个请求,根据请求的URL地址,去找到对应的处理该请求的Action。
我们来模拟一个分配请求的过滤器,它的任务就是根据用户的请求去产生响应的servlet处理请求,而这些servlet其实就是上面的例子当中的productA和productB这类的角色,也就是具体的产品,而它们实现的接口正是Servlet这个抽象的产品接口。
写出filter1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package com.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import com.web.factory.ServletFactory;
//用来分派请求的filter
public class DispatcherFilter implements Filter{
private static final String URL_SEPARATOR = "/";
private static final String SERVLET_PREFIX = "servlet/";
private String servletName;
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {
parseRequestURI((HttpServletRequest) servletRequest);
//这里为了体现我们本节的重点,我们采用一个工厂来帮我们制造Action
if (servletName != null) {
//这里使用的正是简单工厂模式,创造出一个servlet,然后我们将请求转交给servlet处理
Servlet servlet = ServletFactory.createServlet(servletName);
servlet.service(servletRequest, servletResponse);
}else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
//负责解析请求的URI,我们约定请求的格式必须是/contextPath/servlet/servletName
//不要怀疑约定的好处,因为LZ一直坚信一句话,约定优于配置
private void parseRequestURI(HttpServletRequest httpServletRequest){
String validURI = httpServletRequest.getRequestURI().replaceFirst(httpServletRequest.getContextPath() + URL_SEPARATOR, "");
if (validURI.startsWith(SERVLET_PREFIX)) {
servletName = validURI.split(URL_SEPARATOR)[1];
}
}
}
这个filter需要在web.xml中加入以下配置。1
2
3
4
5
6
7
8<filter>
<filter-name>dispatcherFilter</filter-name>
<filter-class>com.web.filter.DispatcherFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>dispatcherFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
配置好了之后,接着我们来制作工厂1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.web.factory;
import javax.servlet.Servlet;
import com.web.exception.ServletException;
import com.web.servlet.LoginServlet;
import com.web.servlet.LoginoutServlet;
import com.web.servlet.RegisterServlet;
public class ServletFactory {
private ServletFactory(){}
//一个servlet工厂,专门用来生产各个servlet,而我们生产的依据就是传入的servletName,
//这个serlvetName由我们在filter截获,传给servlet工厂。
public static Servlet createServlet(String servletName){
if (servletName.equals("login")) {
return new LoginServlet();
}else if (servletName.equals("register")) {
return new RegisterServlet();
}else if (servletName.equals("loginout")) {
return new LoginoutServlet();
}else {
throw new ServletException("unknown servlet");
}
}
}
现在我们可以请求/contextPath/servlet/login来访问LoginServlet,而不再需要添加web.xml的配置.
总结起来就是一个工厂类,一个产品接口(其实也可以是一个抽象类,甚至一个普通的父类,但通常我们觉得接口是最稳定的,所以基本不需要考虑普通父类的情况),和一群实现了产品接口的具体产品,而这个工厂类,根据传入的参数去创造一个具体的实现类,并向上转型为接口作为结果返回。