您的位置:首页 > 编程语言 > Java开发

(三)简单工厂模式详解 分类: java 设计模式

2013-06-09 17:37 676 查看


(三)简单工厂模式详解

分类: java 设计模式2013-06-08
23:55 175人阅读 评论(0) 收藏 举报

设计模式系统设计Java简单工厂模式简单工厂

作者:zuoxiaolong8810(左潇龙),转载请注明出处。

上一章我们着重讨论了代理模式,以及其实现原理,相信如果你看完了整篇博文,应该就对代理模式很熟悉了。

本章我们讨论简单工厂模式,LZ当初不小心夸下海口说不和网络上传播的教学式模式讲解雷同,所以现在感觉写一篇博文压力颇大。

如何来介绍简单工厂呢,LZ着实费了不少心思,这个模式本身不复杂,但其实越是不复杂的模式越难诠释,所以要诠释这个模式,LZ还是觉得一定要使用各位用过的场景,本人不太喜欢自己捏造场景,所以就只好拿我们经常使用的struts2来开刀,但是我们无法使用struts2的源码来诠释,不是因为不行,而是简单工厂相对来说是一个比较低级点的工厂模式,所以struts2这种框架里,很难找到典型的纯粹的使用这个模式的地方,至少LZ目前没找到。

所以LZ会拿我们自己的创造的项目来做例子,但会尽量贴近实际应用。

众所周知,我们平时开发web项目是以spring作为平台,来集成各个组件,比如集成struts2来完成业务层与表现层的逻辑,集成hibernate或者ibatis来完成持久层的逻辑。

struts2在这个过程当中提供了分离数据持久层,业务逻辑层以及表现层的责任,有了Struts2,我们不再需要servlet,而是可以将一个普通的Action类作为处理业务逻辑的单元,然后将表现层交给特定的视图去处理,比如JSP,template等等。

我们来尝试着写一个非常非常简单的web项目,来看看在没有struts2的时候,我们都是怎么过的。

我会省略WEB架构过程当中的很多细节,所以最好是各位亲手做过一些项目,最起码也自己搭建过WEB项目,我相信既然有兴趣来看设计模式,应该都至少有过这种锻炼。

下面我把我们需要的类都列出来,并加上简单的注释。

[html] view
plaincopy

import 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页面,但是这个本次不在考虑范围内,这便是表示层。

以上是我们小型WEB项目大体的结构,可以看到LZ写了三个Servlet,没有写具体的实现到底如何,但是不难猜测,三个servlet的功能分别是进行登录,注销,以及注册新用户的功能。我们的servlet一般都是继承自HttpServlet,因为我们在web.xml配置servlet时,所写入的Class需要实现servlet接口,而我们通常采用的传输协议都是HTTP,所以HttpServlet就是我们最好的选择了,它帮我们完成了基本的实现。

但是这样我们有很多限制,比如我们一个servlet一般只能负责一个单一的业务逻辑,因为我们所有的业务逻辑通常情况下都集中在doPost这样一个方法当中,可以想象下随着业务的增加,我们的servlet数量会高速增加,这样不仅项目的类会继续增加,最最恶心的是,我们每添加一个servlet就要在web.xml里面写一个servlet配置。

但是如果我们让一个Servlet负责多种业务逻辑的话,那我们需要在doPost方法中加入很多if判断,去判断当前的操作。

比如我们将上述三个servlet合一的话,你会在doPost出现以下代码。

[java] view
plaincopy

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");

}

}

这实在是非常烂的代码,因为每次你新加一个操作,都要修改doPost这个方法,而且多个业务逻辑都集中在这一个方法当中,会让代码很难维护与扩展,最容易想到的就是下列做法。(小提示:如果你的项目中出现了这种代码结构,请务必想办法去掉它,你完全可以尽量忘掉Java里还有elseif和swich)

[java] view
plaincopy

protected 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");

}

}

public void login(){

System.out.println("login");

}

public void register(){

System.out.println("register");

}

public void loginout(){

System.out.println("loginout");

}

这样会比第一种方式好一点,一个方法太长,实在不是什么好征兆,等到你需要修改这部分业务逻辑的时候,你就会后悔你当初的写法了,如果这段代码不是亲手写的,那请你放心的在心中吐糟吧,因为这实在不是一个合格的程序员应该写出的程序。

虽然我们已经将各个单一的业务逻辑拆分成方法,但这依然是违背单一原则的,因为我们的servlet应该只是处理业务逻辑,而不应该还要负责与业务逻辑不相关的处理方法定位这样的责任,这个责任应该交给请求方,原本在三个servlet分别处理登陆,注销和注册的时候,其实就是这样的,作为请求方,我请求LoginServlet,就说明我要登陆,处理的servlet不需要再去多考虑请求方到底是要登陆还是注销。

所以我们需要想办法把判断的业务逻辑交给请求方去处理,回想下struts2的做法,我们可以简单模拟下struts2的做法。相信不少同学应该都用过struts2,那么你肯定对以下配置很熟悉。

[html] view
plaincopy

<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在web.xml的配置,我们写出如下filter。

[java] view
plaincopy

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中加入以下配置,这个不多做介绍,直接贴上来。

[html] view
plaincopy

<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>

上述我在filter中稍微加入了一些注释,由于本章的重点是简单工厂模式,所以我们这里突出我们本章的主角,使用简单工厂来创造servlet去处理客户的请求,当然如果你是一个JAVA造诣比较深的程序猿,或许会对这种方式不屑一顾,不过我们不能偏离主题,我们的目的不是模拟一个struts2,而是介绍简单工厂。

下面给出我们的主角,我们的servlet工厂。

[java] view
plaincopy

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");

}

}

}

看到这个,你是不是想说,I go your sister。。。

在看到它之前,你或许在猜想一定是一个利用了多态,继承,封装,反射等等JAVA高级特性写出来的一个非常牛X的工厂类。

但是你真的没看错,这便是我们的主角了,它很简陋,只是简单的根据你传入的servlet名称,返回一个对应的servlet实例,这就是简单工厂!真简单,我去。。。

有关对这分部请求分派更好的优化和架构,LZ会想办法穿插在以后的模式当中,顺便也将设计模式介绍给各位。

不过这个简单工厂最起码帮我们解决了恶心的xml配置,现在我们可以请求/contextPath/servlet/login来访问LoginServlet,而不再需要添加web.xml的配置,虽说这么做,我们对修改是开放的,因为每增加一个servlet,我们都需要修改工厂类,去添加一个if判断,但是LZ个人还是觉得我宁可写if,也不想去copy那个当初让我痛不欲生的xml标签,虽说我刚才还说让你忘掉elseif,我说过吗?好吧。。我说过,但是这只是我们暂时的做法,后面会想办法是用其它的方式消除掉这些。

简单工厂是设计模式当中相对比较简单的模式,它甚至都没资格进入GOF的二十三种设计模式,所以可见它多么卑微了,但就是这么卑微的一个设计模式,也能真正的帮我们解决实际当中的问题,虽说这种解决一般只能针对规模较小的项目。

写到这里,简单工厂模式当中出现的角色,已经很清晰了。我们上述简单工厂当中设计到的类就是Servlet接口,ServletFactory以及各种具体的LoginServlet,RegisterServlet等等。

总结起来就是一个工厂类,一个接口(其实也可以是一个抽象类,甚至一个普通的父类,但通常我们觉得接口是最稳定的),和一群接口的实现类,而这个工厂,根据传入的参数去创造一个具体的实现类,并向上转型为接口作为结果返回。

我们在这里将上述穿插的简单工厂模式抽离出来,注释中有LZ个人的见解,帮助各位理解。类图这里就不再贴了,因为满世界都是。。。

[java] view
plaincopy

//相当于简单工厂模式中的产品接口

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();

}

}

}

其实我们对这一部分逻辑的控制依旧有很多很多的优化余地,但是限于本章介绍的内容,所以我们暂时走到这里,上面所说的很多问题也都是为了介绍之后的模式抛砖引玉,因为我觉得想简单工厂这种没有什么技术上的难度,纯粹是依照业务场景而出现的设计模式,LZ就必须要创造出一个比较真实的业务场景,才能更好的诠释。所以或许会将很多设计模式穿插在这个web项目当中。

好了,本期的简单工厂模式就到这里吧,简单工厂很简单!

下期预告,能不能先不预告。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: