一、工厂设计模式概念
工厂模式(Factory Pattern)很简单,就是用来创建对象实例的,所有的对象实例,都交给工厂类生成。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则,如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦,在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
二、类型
本篇主要介绍三种工厂设计模式的使用:简单工厂、工厂方法、抽象工厂
-
简单工厂(Simple Factory)
简单工厂实际不能算作一种设计模式,它引入了创建者的概念,将实例化的代码从应用代码中抽离,在创建者类的静态方法中只处理创建对象的细节,后续创建的实例如需改变,只需改造创建者类即可,但由于使用静态方法来获取对象,使其不能在运行期间通过不同方式去动态改变创建行为,因此存在一定局限性。
定义:一个工厂方法,依据传入的参数,生成对应的产品对象;
优点:
- 客户与具体实现类松耦合,对于客户来说,他只需要传入对象的类型参数,就可以通过工厂获得所需要的对象实例
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
- 与具体实现类松耦合,却又和工厂绑在了一起,负担太重。当所要生产产品种类非常多时,工厂方法的代码量会很庞大。
- 对于增加新的产品,需要修改工厂方法代码来实现。
角色:
- 抽象产品
- 具体产品
- 具体工厂
- 产品使用者
使用说明:先将产品类抽象出来,比如,苹果和梨都属于水果,抽象出来一个水果类Fruit,苹果和梨就是具体的产品类,然后创建一个水果工厂,分别用来创建苹果和梨;代码如下:
/**
* 抽象产品-水果接口
*/
public interface Fruit {
String getCategory();
}
/**
* 具体实现类-苹果
*/
public static class Apple implements Fruit {
@Override
public String getCategory() {
return "apple";
}
}
/**
* 具体实现类-梨
*/
public static class Pear implements Fruit {
@Override
public String getCategory() {
return "pear";
}
}
/**
* 具体工厂-水果工厂类
*/
public static class FruitFactory {
Fruit createFruit(String type) {
Fruit fruit;
switch (type) {
case "apple":
fruit = new Apple();
break;
case "pear":
fruit = new Pear();
break;
default:
fruit = new Apple();
break;
}
return fruit;
}
}
public static void main(String[] args) {
//产品使用者
FruitFactory mFactory = new FruitFactory();
System.out.println(mFactory.createFruit("apple").getCategory());
System.out.println(mFactory.createFruit("pear").getCategory());
}
以上的这种方式,每当添加一种水果,就必然要修改工厂类,这显然违反了开闭原则(对拓展开放,对修改关闭),亦不可取;所以简单工厂只适合于产品对象较少,且产品固定的需求,对于产品变化无常的需求来说显然不合适。
-
工厂方法(Factory Method)
工厂方法是针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例。
优点:
- 工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点1)
- 同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开放-封闭原则。
缺点:
- 相比简单工厂,实现略复杂。
- 对于某些可以形成产品族的情况处理比较复杂(可以用抽象工厂来实现)。
定义:将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Abstract Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
使用说明:和上例中一样,产品类抽象出来,这次我们把工厂类也抽象出来,生产什么样的产品由子类来决定;
代码如下:水果接口 苹果类和梨类 代码和上例一样
/**
* 抽象工厂类-工厂接口
*/
public interface FruitFactory {
Fruit createFruit();//生产水果
}
/**
* 具体工厂类-苹果工厂
*/
public static class AppleFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Apple();
}
}
/**
* 具体工厂类-梨工厂
*/
public static class PearFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Pear();
}
}
public static void main(String[] args) {
//具体使用
AppleFactory appleFactory = new AppleFactory();
PearFactory pearFactory = new PearFactory();
Apple apple = (Apple) appleFactory.createFruit();//获得苹果
Pear pear = (Pear) pearFactory.createFruit();//获得梨
System.out.println(apple.create());
System.out.println(pear.create());
}
以上这种方式,当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同(即接口)来生成,那么就可以被客户使用,而不必去修改任何已有的代码。(即当有新产品时,只要创建并继承抽象产品;新建具体工厂继承抽象工厂;而不用修改任何一个类)工厂方法模式是完全符合开闭原则的,虽然解耦了,但是问题根本还是没有解决,换汤没换药,如果需要的产品很多的话,需要创建非常多的工厂,所以这种方式的缺点也很明显。
-
抽象工厂(Abstract Factory)
抽象工厂是应对产品族概念的。围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
定义:为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类。
角色:和工厂方法一样
抽象工厂和工厂方法的模式基本一样,区别在于,工厂方法是生产一个具体的产品,而抽象工厂可以用来生产一组相同,有相对关系的产品;重点在于一组,一批,一系列;举个例子,假如生产小米手机,小米手机有很多系列,小米note、红米note等;假如小米note生产需要的配件有825的处理器,6英寸屏幕,而红米只需要650的处理器和5寸的屏幕就可以了;用抽象工厂来实现:
/**
* cpu接口和实现类
*/
public interface Cpu {
String create();
class Cpu650 implements Cpu {
@Override
public String create() {
return "CPU-650";
}
}
class Cpu825 implements Cpu {
@Override
public String create() {
return "CPU-825";
}
}
}
/**
* 屏幕接口和实现类
*/
public interface Screen {
String create();
class Screen5 implements Screen {
@Override
public String create() {
return "ScreenSize-5";
}
}
class Screen6 implements Screen {
@Override
public String create() {
return "ScreenSize-6";
}
}
}
/**
* 工厂接口
*/
public interface PhoneFactory {
Cpu getCpu();//使用的cpu
Screen getScreen();//使用的屏幕
}
/**
* 具体工厂实现类:小米手机工厂
*/
public static class XiaoMiFactory implements PhoneFactory {
@Override
public Cpu getCpu() {
return new Cpu.Cpu825();//高性能处理器
}
@Override
public Screen getScreen() {
return new Screen.Screen6();//6寸大屏
}
}
/**
* 具体工厂实现类:红米手机工厂
*/
public static class HongMiFactory implements PhoneFactory {
@Override
public Cpu getCpu() {
return new Cpu.Cpu650();//高效处理器
}
@Override
public Screen getScreen() {
return new Screen.Screen5();//小屏手机
}
}
public static void main(String[] args) {
PhoneFactory hongMiFactory = new HongMiFactory();
PhoneFactory xiaoMiFactory = new XiaoMiFactory();
System.out.println(hongMiFactory.getCpu().create()+"+" +hongMiFactory.getScreen().create());
System.out.println(xiaoMiFactory.getCpu().create()+"+" +xiaoMiFactory.getScreen().create());
}
以上例子可以看出,抽象工厂可以解决一系列的产品生产的需求,对于大批量,多系列的产品,用抽象工厂可以更好的管理和扩展;
三、区别
- 简单工厂 : 用来生产同一等级结构中的任意产品,可以将客户程序从具体类解耦。(不支持拓展增加产品)
- 工厂方法 :使用继承,把对象的创建委托给子类,用来生产同一等级结构中的固定产品。(支持拓展增加产品)
- 抽象工厂 :对象的创建实现在工厂接口所暴露的方法中,用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
四、三种工厂方式总结:
1、对于简单工厂和工厂方法来说,两者的使用方式实际上是一样的,如果对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;
2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产;
注意事项
(1)工厂类常常采用单例模式(Singleton)。
(2)工厂类拥有基类(定义共同接口)。
工厂模式可以帮助我们针对抽象/接口编程,而不是针对具体类编程,在不同的场景下按具体情况来使用。
获取文章中完整代码示例可访问github:https://github.com/grayee/ecms/tree/master/ecms-web/src/test