(三) 简单工厂模式

转载: http://www.cnblogs.com/zuoxiaolong/p/pattern4.html

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

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

如何来介绍简单工厂呢,LZ着实费了不少心思,这个模式本身不复杂,但其实越是不复杂的模式越难写出特点,因为它太简单。

为了便于各位看后面例子的时候容易理解,LZ这里先给出引自其它地方的简单工厂模式的定义。

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

            定义中最重要的一句话就是,由一个工厂对象决定创建出哪一种产品类的实例,这个LZ在下面会专门举一个现实应用中的例子去展现。

另外给出简单工厂模式的类图,本类图以及上面的定义都引自百度百科。

可以看出,上面总共有三种类,一个是工厂类Creator,一个是产品接口IProduct,一个便是具体的产品,例如产品A和产品B,这之中,工厂类负责整个创建产品的逻辑判断,所以为了使工厂类能够知道我们需要哪一种产品,我们需要在创建产品时传递给工厂类一个参数,去表明我们想要创建哪种产品。

下面LZ将上面的类图转化为更为简单的JAVA代码,便于清晰的展示上面类图中各个类之间的关系。

首先是产品接口。

public interface IProduct {

    public void method();
}

两个具体的产品。

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


            上面便是整个类图转换为JAVA代码的简单例子,将上面标准的简单工厂模式的类图和代码给出,是为了一些新手先熟悉一下这个设计模式的大体框架,方便我们下面使用实际的例子去阐述的时候更加容易理解。

下面LZ就找一个各位基本上都使用过或者将来要使用的一个例子来说明简单工厂模式,我们去模拟一个简单的struts2的功能。

LZ会自己制作一个简单的WEB项目来做例子,其中会忽略掉很多细节,目的是为了突出我们的简单工厂模式。

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

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

我们来尝试着写一个非常非常简单的web项目,来看看在最原始的时候,也就是没有spring,struts2等等这些个开源框架的时候,我们都是怎么过的。

由于LZ会省略WEB架构过程当中的很多细节,所以最好是各位亲手做过一些项目,相对而言看起来会更有体会一些,不过LZ相信既然有兴趣来看设计模式,应该都基本上有过这种锻炼。

下面LZ把我们一个简单的WEB项目中需要的类都列出来,并加上简单的注释。

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出现以下代码。

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)

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

    private void login(){
        System.out.println("login");
    }

    private void register(){
        System.out.println("register");
    }

    private void loginout(){
        System.out.println("loginout");
    }

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

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

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

 <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这个抽象的产品接口。

我们用这个过滤器来消除servlet在web.xml的配置,帮我们加快开发的速度,我们写出如下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
    //不要怀疑约定的好处,因为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中加入以下配置,这个不多做介绍,直接贴上来。

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

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

下面给出我们的主角,我们的servlet工厂,它就相当于上面的Creator。

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

看到这里,是不是有点感觉了呢,我们一步一步去消除servlet的XML配置的过程,其实就是在慢慢的写出一个简单工厂模式,只是在这之中,抽象的产品接口是现成的,也就是Servlet接口。

虽说这些个elseif并不是好代码的征兆,不过这个简单工厂最起码帮我们解决了恶心的xml配置,说起来也算功不可没。

现在我们可以请求/contextPath/servlet/login来访问LoginServlet,而不再需要添加web.xml的配置,虽说这么做,我们对修改是开放的,因为每增加一个servlet,我们都需要修改工厂类,去添加一个if判断,但是LZ个人还是觉得我宁可写if,也不想去copy那个当初让我痛不欲生的xml标签,虽说我刚才还说让你忘掉elseif,我说过吗?好吧。。我说过,但是这只是我们暂时的做法,我们可以有很多种做法去消除掉这些elseif。

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

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

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

我们在这里将上述穿插的简单工厂模式抽离出来,注释中有LZ个人的见解,帮助各位理解。

//相当于简单工厂模式中的产品接口
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已经将刚才的过程给抽离出来,各位可以对比下标准的简单工厂代码,就会发现它们其实是一模一样的设计方式。

为了更加方便各位的对比,LZ这里给出上面JAVA代码的类图。

上面便是我们例子当中有关Servlet创建时,出现的简单工厂模式类图,各位可以和第一次的标准类图对比一下,它们的设计都是一样的。

其实我们针对创建Servlet实例这一部分逻辑的控制依旧有很多很多的优化余地,但是限于本章介绍的内容,所以我们就适可而止。

LZ觉得想简单工厂这种没有什么技术上的难度,纯粹是依照一些业务场景而出现的设计模式,LZ就必须要创造出一个比较真实的业务场景或者现实中的例子,才能更好的诠释。所以本次LZ先拿出了我们经常做的WEB项目,以后LZ也会尽量举一些实际应用的例子。

好了,本期的简单工厂模式就到这里吧,感谢各位的收看!

版权声明



作者:zuoxiaolong(左潇龙)

出处:博客园左潇龙的技术博客--http://www.cnblogs.com/zuoxiaolong

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

时间: 2024-10-21 13:20:49

(三) 简单工厂模式的相关文章

Java实验项目三——简单工厂模式

Program: 请采用采用简单工厂设计模式,为某个汽车销售店设计汽车销售系统,接口car至少有方法print(), 三个汽车类:宝马.奥迪.大众 (属性:品牌,价格),在测试类中根据客户要求购买的汽车品牌, 通过接口car为客户提供相应的汽车对象. Description:通过java反射机制和Properties类的结合使用,实现工厂模式.代码如下: 1.首先是entity包中的一个接口和三个实体类 汽车接口: 1 /* 2 *Description:定义汽车接口 3 * */ 4 5 6

设计模式一:简单工厂模式

概念理解: 1. 解耦:一般通过增加一些抽象层,来实现 功能提供者和功能使用者 两者的解耦隔离.而如果想要扩展功能即增加功能,只需要添加相应的相应的新的功能类,修改中间的抽象类即可,并不用修改使用者部分的代码.并且功能提供者的代码可以复用.而且使得程序更容易理解. 简单工厂模式介绍 一.什么是简单工厂模式? 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个子类(这些子类继承  自一个父类或接口)的实例. 二.简单工厂模式包含的角色: 1.工厂角色(工厂类): 它负责创建所有

设计模式之_简单工厂模式、工厂方法模式、抽象工厂模式 、策略模式、策略与工厂的区别(转)

一.前言 话说十年前,有一个爆发户,他家有三辆汽车(Benz(奔驰).Bmw(宝马).Audi(奥迪)),还雇了司机为他开车.不过,爆发户坐车时总是这样:上Benz车后跟司机说“开奔驰车!”,坐上Bmw后他说“开宝马车!”,坐上 Audi后他说“开奥迪车!”.你一定说:这人有病!直接说开车不就行了?!而当把这个爆发户的行为放到我们程序语言中来,我们发现C语言一直是通过这种方式来坐车的 幸运的是这种有病的现象在OO语言中可以避免了.下面以Java语言为基础来引入我们本文的主题:工厂模式! 二.简介

设计模式学习第三天:2.1简单工厂模式

一. 简单工厂(Simple Factory)模式      Simple Factory模式根据提供给它的数据,返回几个可能类中的一个类的实例.通常它返回的类都有一个公共的父类和公共的方法.      Simple Factory模式实际上不是GoF 23个设计模式中的一员. 二. Simple Factory模式角色与结构: 工厂类角色Creator (LightSimpleFactory):工厂类在客户端的直接控制下(Create方法)创建产品对象.      抽象产品角色Product 

设计模式系列——三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)

原文地址:http://blog.chinaunix.net/uid-25958655-id-4243289.html 简单工厂模式 当需要加法类的时候,调用工厂类的CreateOperate(),要指定制造的Product 例如在大话设计模式P11给的运算工厂例子: public static Operation createOperate(string operate) { Operation oper = null; 12 switch (operate) { case "+":

对比总结三个工厂模式(简单工厂,工厂方法,抽象工厂)

前言 简单工厂模式,工厂方法模式,抽象工厂模式,这三个模式,当然还有单例模式,建造者模式等等,应该是日常工作中常用的,尤其是工厂模式,应该是最最常见的模式,对理解面向对象有重要的实际意义. 简单工厂模式 最简单,最直接,能满足大部分日常需求,不足是工厂类太简单——无法满足开闭原则,对多个产品的扩展不利 工厂方法模式——交给子类去创建 工厂方法模式,有了进步,把工厂类进行改进,提升为一个抽象类(接口),把对具体产品的实现交给对应的具体的子类去做,解耦多个产品之间的业务逻辑. 前面都是针对一个产品族

简单工厂模式( Simple Factory Pattern )

1. 简单工厂模式( Simple Factory Pattern ) 1.1. 模式动机 考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮.矩形按钮.菱形按钮等), 这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式. 1.2

JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)

在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的.但是在一些情况下, new操作符直接生成对象会带来一些问题.举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象. 在这些情况,新对象的建立就是一个 "过程",不仅是一个操作,像一部大机器中的一个齿轮传动. 模式的问题:你如何能轻松方便地构造对象实例,而不必关心构造对象实

设计模式学习一简单工厂模式

简单工厂模式 第一步, 创建父类A(抽象类),抽象方法,以及具体变量 第二步, 创建工厂类父类B,用于实现创建抽象类A 第三步, 创建要子类C,继承父类A,重载父类方法,写方法具体实现,以及变量具体赋值. 第四步, 创建工厂类D,继承工厂类父类B,用来创建具体实现的 子类C (相当于AC  是工厂里面的模板) (BD 是工厂里面生产的产品) 简单工厂模式,备忘