设计模式之 简单工厂模式详解

定义:从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(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也会尽量举一些实际应用的例子。

时间: 2024-10-27 06:51:06

设计模式之 简单工厂模式详解的相关文章

Java研究之学习设计模式-简单工厂模式详解

 简介: 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例. 类图: 从UML类图中,可以看出,简单工厂模式的意思,就是把一个类内部,需要生成的部分,提取出来,变为一个工厂,通过工厂来new对象. 假设我们要吃苹果了,我们可以在代码中new一个苹果出来:当我们需要吃香蕉了,我们在代码中new一个香蕉出来.这种做法你会不会觉得麻烦

二、设计模式总览及工厂模式详解

二.架构师内功心法之设计模式 2.架构师内功心法之设计模式 2.1.课程目标 1.通过对本章内容的学习,了解设计模式的由来. 2.介绍设计模式能帮我们解决哪些问题. 3.剖析工厂模式的历史由来及应用场景. 2.2.内容定位 不用设计模式并非不可以,但是用好设计模式能帮助我们更好地解决实际问题,设计模式最重要的 是解耦.设计模式天天都在用,但自己却无感知.我们把设计模式作为一个专题,主要是学习设计模式 是如何总结经验的,把经验为自己所用.学设计模式也是锻炼将业务需求转换技术实现的一种非常有效 的方

Java研究之学习设计模式-抽象工厂模式详解

 简介:          当每个抽象产品都有多于一个的具体子类的时候,工厂角色怎么知道实例化哪一个子类呢?比如每个抽象产[1] 品角色都有两个具体产品.抽象工厂模式提供两个具体工厂角色,分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化.每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例. 每一个模式都是针对一定问题的解决方案,工厂方法模式针对的是一个产品等级结构:而抽象工厂模式针对的是多个产品等级结构.(摘自百度百科) 话语说得太抽象,程序员最好的表示方式

工厂模式详解

在java中每当看到new 就会想到"具体",代码绑定着具体的类导致代码脆弱尤其是不利于进行修改. new 关键字本身没有错,需要的是我们针对接口编程,从而隔离掉系统可能发生的一系列改变.重要设计原则:对扩展开放,对修改关闭. Pizza orderPizza(String type) { Pizza pizza; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equa

创建和使用解耦——工厂模式详解(工厂方法+抽象工厂)

1.前言 直接new一个对象是最简单的创建对象的方式,但大量出现在业务代码中会带来至少两个问题.1:创建对象的细节直接暴露在业务代码中,修改实现细节必须修改相关的大量客户端代码.2:直接面向具体类型编程,违反了面向接口编程的原则,系统进行扩展时也不得不进行大量修改.要使得系统具有的良好的可扩展性以及后期易于维护,必须实现对产品的获取和对产品的使用解耦.要做到这两点,首先要对客户端代码屏蔽掉创建产品的细节,其次,客户端必须面向产品的抽象编程,利用java的多态特性在运行时才确定具体的产品.而这,正

【设计模式】简单工厂模式

以面向对象的思想和简单工厂模式,写一个C++计算器程序,代码如下: #include <iostream> using namespace std; class Operation { public: Operation(double left, double right) { lhs = left; rhs = right; } const double GetLeft() const { return lhs; } const double GetRight() const { retur

设计模式之简单工厂模式

设计模式之简单工厂模式 动机:         不暴露实例化逻辑来创建对象.通过公共的接口创建新的对象.         这是一个简单的实现,客户端需要一个product,但是client不直接使用new对象,而是通过提供需要的对象信息来找factory得到新的product.         这个factory实例化一个具体的product并返回(转化成抽象的类),client段使用这个抽象的类而不用考虑它具体的实现. 应用举例:        也许工厂模式是使用最多的模式之一.举个例子,一个

设计模式初探—简单工厂模式

为什么要学习设计模式? 可重用.可维护.可扩展.灵活性好 什么是简单工厂模式? 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现. 简单工厂模式的好处? (1)将具体业务和实现进行分离 (2)将多个具体业务之间进行解耦 解决的问题? 单独的类来创造

【幻化万千戏红尘】qianfengDay10-java基础学习:成员内部类、静态内部类、局部和匿名内部类,设计模式之简单工厂模式

课程回顾: 接口:1.属性:public\static\final2.方法:public\abstract 多态:1.静态化(编译时)多态重载2.动态化(运行时)多态重写对象转型 1.向上转型 2.向下转型 今日内容:内部类:定义在类的内部的类1.成员内部类格式:[修饰符] class 类名 { }内部不能有静态的属性和方法,可以访问外部类的属性,也可以调用外部类的方法 在静态方法中的使用格式:外部类 外对象=new 外部类();成员内部类 对象名=外对象.new 成员内部类(); 2.静态内部