设计模式之工厂方法模式

本文介绍了工厂方法模式,其将产品创建工作推迟到子类,可使系统在不修改具体工厂角色时引进新产品。还阐述了该模式在JDBC中的应用,通过Driver和Connection接口统一数据库操作。同时分析了其优缺点、适用场景,最后提及让简单工厂模式改进的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义:工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

下面是工厂方法模式的类图:
在这里插入图片描述
可以看到,上面右半部分是产品抽象和实现体系,左半部分是工厂抽象和实现体系,其中工厂体系依赖于产品体系,每一个工厂负责创造一种产品,这就省去了简单工厂中的elseif判断,又客户端决定实例化一个特定的工厂去创建相应的产品。

下面就是根据上面类图所写的代码
产品接口

public interface Light {

    public void turnOn();

    public void turnOff();
    
}

产品的具体实现

public class BuldLight implements Light{

    public void turnOn() {
        System.out.println("BuldLight On");    
    }

    public void turnOff() {
        System.out.println("BuldLight Off");    
    }

}

public class TubeLight implements Light{

    public void turnOn() {
        System.out.println("TubeLight On");    
    }

    public void turnOff() {
        System.out.println("TubeLight Off");    
    }

}

工厂接口

public interface Creator {

    public Light createLight();
}

工厂实现类

public class BuldCreator implements Creator{

    public Light createLight() {
        return new BuldLight();
    }

}

public class TubeCreator implements Creator{

    public Light createLight() {
        return new TubeLight();
    }

}

测试程序类

public class Client {

    public static void main(String[] args) {
        Creator creator = new BuldCreator();
        Light light = creator.createLight();
        light.turnOn();
        light.turnOff();
        
        creator = new TubeCreator();
        light = creator.createLight();
        light.turnOn();
        light.turnOff();
    }
}

可以看到,我们使用可以随意的在具体的工厂和产品之间切换,并且不需要修改任何代码,就可以让原来的程序正常运行,这也是工厂方法模式对扩展开放的表现,另外工厂方法模式弥补了简单工厂模式不满足开闭原则的诟病,当我们需要增加产品时,只需要增加相应的产品和工厂类,而不需要修改现有的代码。

众所周知,为了统一各个数据库操作的标准,于是有了JDBC的API,它用于给我们这种被称作只会使用现成的东西的程序猿,提供一系列统一的,标准化的操作数据库的接口。其实JDBC的各个类或接口,就是我们操作数据库的过程中各个协助者的抽象,这样的设计是为了让我们对数据库的操作依赖于抽象,还记得我们在设计模式总纲中提到的一句话吗,用抽象构建框架,用细节扩展实现。

JDBC API(即抽象的接口或类)就是整个数据库操作的框架,而各个数据库的驱动就是那些细节。而我们的操作依赖于JDBC API,而不是任何一个具体数据库的细节。

JDBC是如何统一了数据库世界的呢?其实最主要的就是靠两个接口,就统一了世界。。。

来看第一个接口Driver,附上源码。

package java.sql;

import java.sql.DriverPropertyInfo;
import java.sql.SQLException;

/**
 * The interface that every driver class must implement.
 */
public interface Driver {

    Connection connect(String url, java.util.Properties info)
        throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
             throws SQLException;

    int getMajorVersion();

    int getMinorVersion();

    boolean jdbcCompliant();
} 

由于篇幅,删掉了很多注释,只保留了这个类注释的第一句话,翻译过来是这是一个任何驱动类都必须实现的接口。多么霸气啊。也就是每个数据库厂商都必须实现这个接口来提供JDBC服务,即java数据库连接服务,来方便程序猿对数据库应用编程。

我们先忽略掉下面的五个方法,第一个方法毫无疑问是这个接口中相对而讲最重要的方法了,即创造一个数据库连接,虽然方法名称是connect,但是我觉得这个方法完全可以改为createConnection。

提到Connction,这个接口我们一定不陌生,它的源码也已经在代理模式一章出现过,这里我们再次让它出场,我依旧会删掉它的大部分方法,限于篇幅。

package java.sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * <P>A connection (session) with a specific
 * database. SQL statements are executed and results are returned
 * within the context of a connection.
 * <P>
 */
public interface Connection  extends Wrapper {

    Statement createStatement() throws SQLException;

    PreparedStatement prepareStatement(String sql) throws SQLException;

}

以上便是Connection接口,这里只留下了两个方法,这两个方法相信各位读者都非常熟悉,它们都是我们最经常用的方法之二。

以上两个接口作为JDBC API的一部分,它们相当于告诉了数据库生产厂商两个要求。

第一,数据库厂商要提供一个数据库驱动类,它的作用可以是可以创造数据库连接,而这个数据库连接向上转型为我们JDBC的Connection。
第二,数据库厂商要提供一个数据库连接的实现类,这个实现类可以执行具体数据库的各个操作,比如帮我们执行SQL,返回执行结果,关闭连接等等。

我们都知道mysql的驱动类位于com.mysql.jdbc.Driver,而mysql的connection实现类也在这个包中,名称是ConnectionImpl,而相应的oracle也有驱动类,位于oracle.jdbc.driver.OracleDriver,相应的oracle也有connection实现类,位于oracle.jdbc.OracleConnectionWrapper。一般每个数据库都会有一个Connection的扩展接口,这个接口的作用是提供使用者针对当前数据库特殊的操作。

这里我们忽略掉这些中间接口以及抽象类,我给出上述六个类的UML图,如果各位以前知道工厂方法模式的话,各位看一下,它们的关系是否很熟悉。

在这里插入图片描述我们对比上面标准的工厂方法模式,就会发现它们的关系不正是工厂方法模式吗?

工厂方法模式就是提供一个抽象的工厂,一个抽象的产品,在上述当中相当于Driver(数据库连接工厂)和Connection(抽象产品),实现的一方需要提供一个具体的工厂类(比如mysql驱动)和一个具体的产品(比如mysql数据库连接)。

客户端调用时不依赖于具体工厂和产品(即到底是mysql驱动,mysql数据库连接还是oracle驱动,oracle连接,我们程序猿不需要管的,我们只管使用抽象的driver和connection,对吧?),而是依赖于抽象工厂和抽象产品完成工作。

DriverMananger在这个设计当中扮演者一个管理者的角色,它帮我们管理数据库驱动,让我们不需要直接接触驱动接口,我们获取连接只需要和DriverManager打交道就可以,也就是说客户端依赖于DriverManager和Connection就可以完成工作,不再需要与Driver关联,所以上述说我们依赖于Driver和Connection,现在DriverManager帮我们管理Driver,那我们只需要依赖于DriverManager和Connection就可以了。

在类图中拉出了DriverManager的方法,其中的registerDriver方法正是我们注册数据库驱动的入口。来看看mysql的Driver中做了什么,oracle类似。

public class Driver extends NonRegisteringDriver
  implements java.sql.Driver
{
  public Driver()
    throws SQLException
  {
  }

  static
  {
    try
    {
      DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!");
    }
  }
}

可以看到,在类构造方法中,加入了registerDriver这个方法,所以当我们使用class.forName加载驱动的时候,将会把mysql驱动注册到DriverManager,这时DriverManager中就会持有Mysql驱动所必要的信息,我们就可以使用DriverManager来获得具体的mysql连接了,当然,你要提供url,用户名和密码。

工厂方法模式的优点:从类关系上来说,它可以让客户端与具体的工厂与产品解耦,从业务角度来说,它让客户端与具体的产品解耦。

适用场景:我们需要一个产品帮我们完成一项任务,但是这个产品有可能有很多品牌(像这里的mysql,oracle),为了保持我们对产品操作的一致性,我们就可能要用到工厂方法模式。

不足:若产品数量巨多,而且需要我们亲手去逐个实现的时候,工厂方法模式就会增加系统的复杂性,到处都是工厂类和产品类。

当然这也不是绝对,比如我们经常使用的HashSet和ArrayList,也是使用的工厂方法模式,各位看下他们的类图就看出来了。
在这里插入图片描述上述这便是工厂方法模式另外一种用法了,刚才因为我们不关心真正的产品是什么,所以我们直接使用抽象接口操作。但是我们使用iterable和iterator的时候,我们是关心真正产品的特性的,所以为了使用产品的特性,我们就需要使用产品特有的接口了,比如特殊的SortedSet可排序,比如ArrayList可以有重复元素,可以根据索引获取元素等等。当然你依然是可以使用iterable和iterator的,但是不管你用什么,在这种场景下,产品是你自己选的,一句话,你随便。。。

两种使用方式一种是对使用者透明的,一种是不透明的,一种是使用者对具体的产品不关心,这种情况下,一般产品提供的功能是类似的。一种是使用者非常了解产品的特性,并想使用产品的特性,这种情况下,一般产品只提供最基本的一致的功能,但每个产品都会有自己独特的一面。

但是个人觉得真正做项目的过程当中很少用到工厂方法模式,这个模式更多的是帮助我们理解现有的开源项目,就像现在,你是不是对JDBC的大体框架有了一定认识了呢,如果你不知道这个模式,可能看源码会觉得一头雾水呢。

规则只是用来指导你的,不是用来限制你的,只要设计合理,你的设计就是规则!

我们可以参考struts2的做法,即每一个Servlet我们都可以采用注解去设置它的名称,或者叫url,然后我们让我们的简单工厂依据这个去实例化我们的servlet。

根据以上方案,我们需要按照以下步骤让我们的简单工厂彻底死翘翘。
1.需要声明一个注解,它可以用来给servlet标识它的名称。
2.需要声明一个注解的处理器,用来处理我们的注解,主要作用是通过一个CLASS文件,去获得它的注解信息。
3.基于性能,我们需要将servlet与名称的映射与应用的生命周期绑定,并且这份映射在整个应用当中有且仅有一份,且不可更改。
4.让我们用于分派请求的过滤器,使用映射信息将客户请求对应到相应的servlet去处理,并且将分派逻辑移回过滤器,从而彻底删除简单工厂,即ServletFactory。

特别说一下,这四步当中,其中第三步是可选的,但也是必须的,因为如果不做这种处理,那么你就等着你的项目N长时间打开一个网页吧。

好了,工厂方法模式就给各位分享到这吧,感谢各位的欣赏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值