从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
代码示例:
产品接口
public interface IProduct {
public void method();
}
产品A和产品B
public class ProductA implements IProduct{
public void method() {
System.out.println("产品A方法");
}
}
public class ProductB implements IProduct{
public void method() {
System.out.println("产品B方法");
}
}
工厂类
public 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;
}
}
}
客户端调用
public class Client {
public static void main(String[] args) {
IProduct product1 = Creator.createProduct("A");
product1.method();
IProduct product2 = Creator.createProduct("B");
product2.method();
}
}
一个简单的web工程,大致需要下面的各种类:
import javax.servlet.http.HttpServlet;
//这个类在代理模式出现过,是我们的数据源连接池,用来生产数据库连接。
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一般只能负责一个单一的业务逻辑,因为我们所有的业务逻辑通常情况下都集中在doPost这样一个方法当中,可以想象下随着业务的增加,我们的servlet数量会高速增加,这样不仅项目的类会继续增加,最最恶心的是,我们每添加一个servlet就要在web.xml里面写一个servlet配置。
但是如果我们让一个Servlet负责多种业务逻辑的话,就需要在doPost方法中加入很多if判断,去判断当前的操作。例如如下代码:
protected 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");
}
}
这样每次新增加一个方法,都要修改这个类,而且多个业务逻辑都集中在这一个方法当中,会让代码很难维护与扩展。或许可以分别把处理逻辑拆分在独立的方法中,但是依然会导致类的代码量的不断膨胀。
sevlet应该只是处理业务逻辑,而方法的定位应该交由请求方,所以模拟一下Struts2的过滤器。根据用户的请求去产生响应的servlet处理请求,而这些servlet其实就是上面的例子当中的productA和productB这类的角色,也就是具体的产品,而它们实现的接口正是Servlet这个抽象的产品接口。
filter类:
package 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
//不要怀疑约定的好处,约定优于配置
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中添加如下配置:
<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>
servlet工厂:
package 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的配置,虽说这么做,我们对修改是开放的,因为每增加一个servlet,我们都需要修改工厂类,去添加一个if判断,但是这样省去了xml标签的配置。
简单工厂是设计模式当中相对比较简单的模式,一般只针对规模较小的项目。
总结起来就是一个工厂类,一个产品接口(其实也可以是一个抽象类,甚至一个普通的父类,但通常我们觉得接口是最稳定的,所以基本不需要考虑普通父类的情况),和一群实现了产品接口的具体产品,而这个工厂类,根据传入的参数去创造一个具体的实现类,并向上转型为接口作为结果返回。
总体代码结构
//相当于简单工厂模式中的产品接口
interface Servlet{}
//相当于简单工厂模式中的抽象父类产品。
//注意,简单工厂在网络上的资料大部分为了简单容易理解都是只规划了一个产品接口,但这不代表就只能有一个,设计模式的使用要灵活多变。
class HttpServlet implements Servlet{}
//具体的产品
class LoginServlet extends HttpServlet{}
class RegisterServlet extends HttpServlet{}
class LoginoutServlet extends HttpServlet{}
//产品工厂
public class ServletFactory {
private ServletFactory(){}
//典型的创造产品的方法,一般是静态的,因为工厂不需要有状态。
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 RuntimeException();
}
}
}
当然工厂类中的elseif结构,这是简单工厂的诟病,它对扩展开放,对修改也开放。在项目规模相对较小或者说具体的产品类相对不多的情况下(针对本例的描述,特指的servlet数量不多的情况下),其实这种设计还是可以接受的,因为少量的elseif可以换来我们开发上的便利。
但是在servlet数量急剧上升的时候,工厂类就会变得非常臃肿和复杂,变得难以维护和阅读。我们可以参考struts2的做法,即每一个Servlet我们都可以采用注解去设置它的名称,或者叫url,然后我们让我们的简单工厂依据这个去实例化我们的servlet。
根据以上方案,我们需要按照以下步骤。
1.需要声明一个注解,它可以用来给servlet标识它的名称。
2.需要声明一个注解的处理器,用来处理我们的注解,主要作用是通过一个CLASS文件,去获得它的注解信息。
3.基于性能,我们需要将servlet与名称的映射与应用的生命周期绑定,并且这份映射在整个应用当中有且仅有一份,且不可更改。
4.让我们用于分派请求的过滤器,使用映射信息将客户请求对应到相应的servlet去处理,并且将分派逻辑移回过滤器,从而彻底删除简单工厂,即ServletFactory。
特别说一下,这四步当中,其中第三步虽然可选,但是必须,因为如果不做这种处理,那么项目打开一个网页会需要更多的时间。