本文主要会介绍三种工厂模式:简单工厂,工厂方法,抽象工厂。其中简单工厂并不属于 GoF 23 种设计模式,工厂方法和抽象工厂模式则是属于 GoF 23 种设计模式之中的 2 种。工厂模式作为一种非常常用的设计模式,在日常开发中非常常见,也是一种非常基础的设计模式。
知识点
简单工厂模式
工厂方法模式
抽象工厂模式
三种工厂模式对比
工厂模式能解决什么问题
简单工厂模式
简单工厂设计模式:Simple Factory Pattern
,指的是由一个工厂对象来决定创建具体的产品实例,简单工厂模式并不属于 GoF 23 种设计模式之一,但是我们在平常开发中也运用得非常广泛。
示例
下面我们就农场里面种植水果来举例看看简单工厂模式该怎么写(这里我们需要新建一个 simple
目录,相关类创建在 simple
目录下)。
- 新建一个产品接口
IProduct.java
,接口中定义了一个种植方法。
package simple;
public interface IProduct {
void grow();//种植水果
}
- 新建一个苹果类
Apple.java
来实现产品接口。
package simple;
public class Apple implements IProduct {
@Override
public void grow() {
System.out.println("种植苹果");
}
}
- 新建一个桔子类
Orange.java
来实现产品接口。
package simple;
public class Orange implements IProduct {
@Override
public void grow() {
System.out.println("种植桔子");
}
}
如果不用工厂模式那么创建 Apple 和 Orange 对象都直接 new 一个出来就好了,但是这种方式很不友好,一旦一个对象的创建非常复杂,那么会非常不方便,所以需要一个工厂来帮助我们创建具体的产品类,从而隐藏创建细节。
- 新建一个简单工厂类
SimpleFactory.java
来创建产品,其中定义了一个创建方法,根据产品类型来决定创建哪种产品。
package simple;
public class SimpleFactory {
public IProduct createProduct(String productType){
if("apple".equals(productType)){
return new Apple();
}else if("orange".equals(productType)){
return new Orange();
}
return null;
}
}
- 新建一个测试类
TestSimpleFactory.java
来测试一下。
package simple;
public class TestSimpleFactory {
public static void main(String[] args) {
SimpleFactory factory = new SimpleFactory();
IProduct apple = factory.createProduct("apple");
apple.grow();//输出:种植苹果
IProduct orange = factory.createProduct("orange");
orange.grow();//输出:种植桔子
}
}
接下来我们需要先执行 javac simple/*.java
命令进行编译。然后再执行 java simple.TestSimpleFactory
命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
这时候我们就将创建细节隐藏了,全部产品都通过 SimpleFactory 来
帮忙创建。但是这种写法仍然存在两个问题:
假如我调用 createProduct
方法参数写错了,比如 apple 写成了 aple,这时候代码编译可以通过,运行的时候才能发现问题。
如果产品很多,那么 SimpleFactory
类中会产生大量的 if 分支。
所以为了解决上面的两个问题,我们可以再优化一下 SimpleFactory
类中的 createProduct
方法。
改造一下 SimpleFactory 类,新增一个 createProduct2
方法。
package simple;
public class SimpleFactory {
public IProduct createProduct(String productType){
if("apple".equals(productType)){
return new Apple();
}else if("orange".equals(productType)){
return new Orange();
}
return null;
}
public IProduct createProduct2(Class<? extends IProduct> clazz) throws Exception {
if (null == clazz){
throw new Exception("无法识别的产品");
}
return clazz.newInstance();
}
}
- 同样的,我们再重新建立一个测试类
TestSimpleFactory2.java
,改成调用createProduct2
方法来获取产品对象。
package simple;
public class TestSimpleFactory2 {
public static void main(String[] args) throws Exception {
SimpleFactory factory = new SimpleFactory();
IProduct apple = factory.createProduct2(Apple.class);
apple.grow();//输出:种植苹果
IProduct orange = factory.createProduct2(Orange.class);
orange.grow();//输出:种植桔子
}
}
同样的我们需要再执行 javac simple/*.java
命令进行编译。然后再执行 java simple.TestSimpleFactory2
命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
可以看到,这种写法完美的解决了上面存在的两个问题(不过一般我们的工厂类都会将创建对象实例的方法设置成静态方法)。
简单工厂模式适用场景
简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。
简单工厂模式存在的问题
假如每种产品创建不仅仅只是实例化一个对象,还有其它逻辑需要处理,那么我们无法直接使用一句反射语句来创建对象,所以还是避免不了要写很多 if 或者 switch 循环分支。这样每新增一个产品我们都需要修改简单工厂类,违背了开闭原则,而且随着产品越来越丰富,工厂的职责会变得越来越多,久而久之会越来越难以维护。
为了弥补简单工厂方法的不足之处,所以就有了工厂方法模式。
工厂方法模式
工厂方法模式:Fatory Method Pattern
,主要用来解决简单工厂模式存在的问题。其是指定义一个创建对象的接口,然后创建不同的具体工厂来创建对应的产品。工厂方法让类的实例化推迟到工厂子类中进行,在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节。
工厂方法模式中假如需要新增产品,只需要再新建工厂实现类,无需修改源码,符合开闭原则。
示例
我们还是以上面农场种植水果举例进行说明,产品接口和产品类不做修改,为了方便大家练习的时候对照,所以我还是把产品接口和产品类在下面重写一下(这里我们需要新建一个 method 目录,相关类创建在 method 目录下)。
- 新建一个产品接口
IProduct.java
。
package method;
public interface IProduct {
void grow();
}
- 新建一个具体产品苹果类
Apple.java
。
package method;
public class Apple implements IProduct {
@Override
public void grow() {
System.out.println("种植苹果");
}
}
- 新建一个具体产品桔子类
Orange.java
。
package method;
public class Orange implements IProduct {
@Override
public void grow() {
System.out.println("种植桔子");
}
}
现在我们需要将工厂也抽象化,新建一个工厂接口 IFarmFactory.java
,定义一个 create
方法,这个方法的返回值就是产品。
package method;
public interface IFarmFactory {
IProduct create();//创建产品
}
新建一个生产苹果的具体工厂类 AppleFactory.java
,并实现工厂接口 IFarmFactory
。
package method;
public class AppleFactory implements IFarmFactory {
@Override
public IProduct create() {
return new Apple();//苹果工厂生产苹果
}
}
新建一个生产桔子的具体工厂类 OrangeFactory.java
,并实现工厂接口 IFarmFactory
。
package method;
public class OrangeFactory implements IFarmFactory {
@Override
public IProduct create() {
return new Orange();//桔子工厂生产桔子
}
}
到这里大家应该很明白工厂方法和简单工厂的区别了,简单工厂就是所有产品都由一个工厂类一个方法来创建,而工厂方法将工厂的职责也进行细化了,每种产品都由自己特定的工厂来生产,这也是单一职责原则的体现。
最后新建一个测试类 TestFactoryMethod.java
来进行测试。
package method;
public class TestFactoryMethod {
public static void main(String[] args) {
IFarmFactory appleFactory = new AppleFactory();
IProduct apple = appleFactory.create();
apple.grow();//输出:种植苹果
IFarmFactory orangeFactory = new OrangeFactory();
IProduct orange = orangeFactory.create();
orange.grow();//输出:种植桔子
}
}
接下来我们需要先执行 javac method/*.java
命令进行编译。然后再执行
java method.TestFactoryMethod
命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
这时候如果需要新增其它商品,需要创建两个类:一个具体产品类,一个具体工厂类,所以说灵活的同时付出的代价就是类的数量变多了,学习到后面大家就知道,这也是设计模式的共性。
工厂方法模式的适用场景
工厂方法模式主要适用于以下场景:
- 创建对象需要大量重复的代码。
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
- 一个类通过其子类来指定创建哪个对象。
工厂方法模式的缺点
工厂方法模式的缺点也是很明显的,每新增一个产品就需要新增两个类,一旦产品数量上来了,类的个数也会过多,就会增加了系统的复杂度,也使得系统更加抽象难以理解。
抽象工厂模式
抽象工厂模式:Abstract Factory Pattern
,是指提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
抽象工厂模式强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象时需要大量重复的代码。此时我们就需要提供一个产品类的库,使得所有的产品以同样的接口出现,这样客户端就可以不依赖于具体实现。
什么是产品族
在学习抽象工厂模式之前,我们需要先了解一下什么叫产品族。产品族指的就是相似的产品,比如说我们常用的手机,有华为、小米、OPPO,这些不同品牌的手机就属于同一个产品族,它们隶属于不同的公司,也就是需要由不同的工厂进行生产,这些工厂都可以共用同一个抽象方法来生产手机,这就是抽象工厂模式的本质,将相似的产品(同一产品族)抽象出公共的方法,统一对外接口。
示例
接下来我们还是以农场种植水果举例进行说明。为了方便举例,我们只将水果和农场分为南北两个品种,比如南方农场生产的苹果和桔子称之为南方苹果和南方桔子,而北方农场生产的苹果和桔子就称之为北方苹果和北方桔子(这里我们需要新建一个 abstractFactory
目录,相关类创建在 abstractFactory
目录下)。
- 新建一个苹果的接口
IApple.java
,定义一个种植苹果的方法。
package abstractFactory;
public interface IApple {
void growApple();//种植苹果
}
- 新建一个具体产品“南方苹果”类
SouthApple.java
来实现IApple
接口。
package abstractFactory;
public class SouthApple implements IApple {
@Override
public void growApple() {
System.out.println("种植南方苹果");
}
}
- 新建一个桔子的接口
IOrange.java
,定义一个种植桔子的方法。
package abstractFactory;
public interface IOrange {
void growOrange();//种植桔子
}
- 新建一个具体产品“南方桔子”类
SouthOrange.java
来实现IOrange
接口。
package abstractFactory;
public class SouthOrange implements IOrange {
@Override
public void growOrange() {
System.out.println("种植南方桔子");
}
}
以上就是一个将产品抽象化的过程,接下来我们就需要将产品交给工厂进行创建。
- 新建一个抽象工厂接口
IFactory.java
,定义两个方法:一个用来创建苹果对象,一个用来创建桔子对象。
package abstractFactory;
public interface IFactory {
IApple createApple();
IOrange createOrange();
}
- 新建一个具体工厂“南方工厂”类
SouthFarmFactory.java
来实现IFactory
接口,并实现其中的两个抽象方法。
package abstractFactory;
public class SouthFarmFactory implements IFactory {
@Override
public IApple createApple() {
return new SouthApple();//南方农场生产南方苹果
}
@Override
public IOrange createOrange() {
return new SouthOrange();//南方农场生产南方桔子
}
}
- 现在我们可以新建一个测试类
TestAbstractFactory.java
来进行测试。
package abstractFactory;
public class TestAbstractFactory {
public static void main(String[] args) {
IFactory southFarmFactory = new SouthFarmFactory();//构建南方农场
IApple apple = southFarmFactory.createApple();//获得南方苹果
apple.growApple();//输出:种植南方苹果
IOrange orange = southFarmFactory.createOrange();//获得南方桔子
orange.growOrange();//输出:种植南方桔子
}
}
- 接下来我们需要先执行
javac abstractFactory/*.java
命令进行编译。然后再执行java abstractFactory.TestAbstractFactory
命令运行测试类。
这就是一个抽象工厂的设计模式示例,不过上面我并没有完成北方农场的示例,这个就作为本次实验留给大家的作业,大家可以自己把北方农场生产北方苹果和北方桔子的类完善了并进行测试,自己动手才能加深印象。
假如这时候又有一个新的农场要建立生产一样的产品,那么就可以再新建一个对应的产品类和对应的工厂类就可以实现了。
抽象工厂模式的适用场景
抽象工厂模式适用于我们有一系列类似的产品(比如上面的南方苹果和北方苹果,还有华为手机和小米手机),然后这些产品的实现又有细节上的不同,那么这时候就可以利用抽象工厂模式来将产品进行抽象化。
抽象工厂模式的缺点
根据上面的例子再结合设计模式七大原则,其实我们可以发现抽象工厂有一个很大的缺点,那就是扩展产品相当困难,比如示例中现在农场想种植西瓜,那么我们需要修改工厂的源码,新增种植西瓜的方法,这样的话抽象工厂、具体工厂都需要修改,显然违背了开闭原则。所以抽象工厂模式使用的前提必须是产品比较稳定,不会轻易作出修改,否则后期的维护将会非常困难。
总结
工厂模式只是一个统称,大部分人都知道工厂模式,但是却并不是所有人都知道工厂模式也分为了 3 种。在实际开发过程中,当我们需要隐藏对象的创建细节时,可以考虑使用工厂模式,但是却更应该根据实际业务场景,选择一种比较合适的工厂模式。
参考资料:https://www.lanqiao.cn/courses/3031
如果您觉得这篇文章对你有帮助就点个赞吧!
关注我们收获更多编程好课和实用干货!