在Java(或者叫做面向对象语言)的世界中,工厂模式被广泛应用于项目中,也许你并没有听说过,不过也许你已经在使用了。 简单来说,工厂模式的出现源于增加程序序的可扩展性,降低耦合度。之所以叫做工厂模式,是用工厂生产产品来形象的比喻代码中生产对象的过程。总体来说,工厂模式分为以下几种:
我们模拟一种场景,有一家汽车厂(AutoFactory)要生产汽车,现在主要生产小轿车(Car)和大巴车(Bus),那用代码模拟如下:
首先“设计”一个汽车原型(定义汽车接口),这个接口体现了所有汽车的共性:
public interface Auto { //所有汽车都可以被驾驶 public void drive(); }
接下来我们“设计”两种汽车:小轿车和大巴车:
//小轿车 public class Car implements Auto{ @Override public void drive(){ System.out.println(“小轿车启动了”); } }
//大巴车 public class Bus implements Auto{ @Override public void drive(){ System.out.println(“大巴车启动了”); } }
开始“建厂”了,我们实现一个简单工厂类:
public class AutoFactory{ //生产汽车 public Auto produce(String name){ if("car".equals(name)){ return new Car(); } else if("bus".equals(name)){ return new Bus(); } } }
一切就绪,我们开始生产汽车了,先生产一辆小轿车:
AutoFactory factory = new AutoFactory(); Auto car = factory.produce("car"); car.drive();
简单工厂模式实现了生成产品类的代码跟具体的产品实现分离,在工厂类中你可以添加所需的生成产品的逻辑代码,但是问题来了,这不符合“开放-封闭”原则的,也就是说 对扩展开放,对修改关闭 ,如果你要加一个新的汽车类型还需要修改produce方法,为解决这个问题,从而引入了工厂方法模式(Factory Method Pattern)。
工厂为了扩大市场,现在要开始生产卡车(Truck)了,于是我们设计一辆卡车:
//卡车 public class Truck implements Auto{ @Override public void drive(){ System.out.println(“卡车启动了”); } }
如果按照简单工厂的逻辑,需要修改produce方法(也就是我们要改造已有工厂),这样会影响已有生产,怎么办呢?解决办法是再新建新的工厂:
首先我们“设计”一个工厂原型(工厂接口):
public interface IAutoFactory{ //生产汽车 public Auto produce(String name); }
然后将原来的工厂简单改造符合设计好的工厂原型(实现接口即可,所有逻辑不变):
public class AutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(String name){ if("car".equals(name)){ return new Car(); } else if("bus".equals(name)){ return new Bus(); } } }
好的,接下来为了生产卡车,我们要为卡车单独建厂:
public class TruckAutoFactory implements IAutoFactory{ //生产卡车 @Override public Auto produce(String name){ return new Truck(); } }
开始生产卡车:
AutoFactory factory = new TruckAutoFactory(); Auto car = factory.produce(null); car.drive();
这里的抽象工厂中,我们为了减少改造成本,在简单工厂基础上做最小修改,理论上produce参数可以没有,然后为小轿车、大巴车和卡车分别建立工厂,分别生产。这样如果有了新的类型的车,可以不改动之前的代码,新建一个“工厂”即可,做到“开放封闭原则”。
虽然看似类变多了,逻辑复杂了,但是这种改造带来的好处也是显而易见的:不变动老的代码,通过新建工厂类完成新功能的添加,老功能不变,最大限度的避免动了老代码的逻辑导致引入新的bug。
工厂方法的结构图如下:
我们继续针对汽车工厂说明,由于接下来工厂需要继续扩大规模,开始涉足汽车配件,上层决定涉足汽车大灯业务,针对已有车型生产前大灯。但是如果按照工厂方法模式,需要再继续新建一批工厂,针对每种汽车再建N个工厂,考虑到成本和简单性,针对对已有汽车工厂改造。
首先“设计”大灯原型:
//大灯 public interface Light { //开灯 public void turnOn(); }
再“设计”小轿车、大巴车和卡车大灯:
//小轿车大灯 public class CarLight implements Light{ @Override public void tunOn(){ System.out.println(“小轿车大灯亮了”); } }
//大巴车大灯 public class BusLight implements Light{ @Override public void tunOn(){ System.out.println(“大巴车大灯亮了”); } }
//卡车大灯 public class TruckLight implements Light{ @Override public void tunOn(){ System.out.println(“卡车大灯亮了”); } }
接下来我们重新“设计”原有的汽车工厂(修改工厂接口或者抽象工厂类)
public interface IAutoFactory{ //生产汽车 public Auto produce(); //生产大灯 public Light produceLight(); }
好的,改造工厂,首先改造小轿车工厂:
public class CarAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Car(); } //生产车灯 @Override public Light produceLight(){ return new CarLight(); } }
改造大巴车工厂:
public class BusAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Bus(); } //生产车灯 @Override public Light produceLight(){ return new BusLight(); } }
改造卡车工厂:
public class TruckAutoFactory implements IAutoFactory{ //生产汽车 @Override public Auto produce(){ return new Truck(); } //生产车灯 @Override public Light produceLight(){ return new TruckLight(); } }
开始生产:
//生产小轿车和小轿车大灯 AutoFactory factory = new CarAutoFactory(); Auto car = factory.produce(); car.drive(); Light light = factory.produceLight(); light.turnOn();
//生产大巴车和小大巴车大灯 AutoFactory factory = new BusAutoFactory(); Auto bus = factory.produce(); bus.drive(); Light light = factory.produceLight(); light.turnOn();
//生产卡车和卡大灯 AutoFactory factory = new TruckAutoFactory(); Auto truck = factory.produce(); truck.drive(); Light light = factory.produceLight(); light.turnOn();
抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类,抽象工厂模式较好的实现了“开放-封闭”原则,是三个模式中较为抽象,并具一般性的模式。
抽象工厂模式示意图如下:
简单工厂模式强调生产(对象实例化)的统一性,通过逻辑判断方式,统一生产(对象的生成)。这样用户做的只是一个选择题,不需要再单独寻找和熟悉具体的产品(接口实现类)了。
工厂方法强调一件商品一个工厂,克服了简单工厂违背“开放-封闭”原则的缺点,又保持了封装对象创建过程的优点。
抽象工厂强调一个工厂生产多种相关或者相互依赖商品(就好像一个工厂里有多条生产线),就如本文中的例子一样,车和车灯是相互依赖的(车灯需要与具体的车类型匹配),于是将两种商品放在一个工厂的两条生产线上,这样在新建工厂的同时,即可很容易的关联两种产品。
在 Hutool 中,Hutool-db模块为了简化和抽象连接池的创建,使用了 工厂方法模式 ,首先定义了 DSFactory
:
//数据源工厂 public abstract class DSFactory { //获取数据源 public abstract DataSource getDataSource(String group); }
然后分别创建了: HikariDSFactory
、 DruidDSFactory
、 TomcatDSFactory
、 DbcpDSFactory
、 C3p0DSFactory
几种常见连接池的工厂实现,这样用户可以很容易的使用对应的连接池工厂创建需要的连接池数据源(DataSource)。
同样,用户也可以自己继承 DSFactory
实现抽象方法 getDataSource
来自定义数据源。
在此基础上,对于数据源工厂的创建,又使用了 简单工厂模式 ,代码如下:
public static DSFactory create(Setting setting) { DSFactory dsFactory; try { dsFactory = new HikariDSFactory(setting); } catch (NoClassDefFoundError e1) { try { dsFactory = new DruidDSFactory(setting); } catch (NoClassDefFoundError e2) { try { dsFactory = new TomcatDSFactory(setting); } catch (NoClassDefFoundError e3) { try { dsFactory = new DbcpDSFactory(setting); } catch (NoClassDefFoundError e4) { try { dsFactory = new C3p0DSFactory(setting); } catch (NoClassDefFoundError e5) { // 默认使用Hutool实现的简易连接池 dsFactory = new PooledDSFactory(setting); } } } } } log.debug("Use [{}] DataSource As Default", dsFactory.dataSourceName); return dsFactory; }
通过try的方式,按照优先级尝试创建对应连接池的工厂类,如果用户没有引入对应连接池库,就会报 NoClassDefFoundError
异常,从而尝试创建下一个连接池工厂,依次类推,直到发现用户未引入任何连接池库,则使用Hutool默认的简单连接池 PooledDSFactory
。通过这种方式,简化了用户对连接池的选择配置。