抽象工厂模式 - “V我50,一键生成全家桶”

前言

Hi, 我是Rike,欢迎来到我的频道~

本篇为大家带来的是设计模式-抽象工厂方法模式。前面我们已经学习了简单工厂模式 - "轻松实例化对象的魔法工坊~"《工厂方法模式 - "造物主的智慧模具!"》,如果在业务开发中经常使用,就会发现:

  • 如果每个产品都声称了工厂,那系统内工厂的数量会急剧上升。
  • 如果多个产品之间有相似关联,需要通过多个工厂共同管理产品。

要是有一个大型工厂能够管理各工厂产品的产出就好啦,抽象工厂模式因此诞生。


一、介绍

在工厂方法模式中,通过工厂类只能生产一类产品,无法生产一个产品簇。因此抽象工厂模式孕育而生。

但在学习抽象工厂模式之前,我们需要先了解两个概念:产品结构和产品簇。

(一)产品结构和产品簇

产品结构:指产品内部各部分之间的关系,描述了产品内部之间的层次关系和依赖关系。

产品簇:指一组相关的产品,这些产品在某个特定领域或功能领域下具有共同的特征或属性。即,产品簇是由多个产品结构组成。

其关系如下图所示:

在编程中,可以理解为:产品结构是一个接口及其实现类,产品簇是多个产品接口及其实现类的集合。


在后续示例当中,有如下定义:

  • 命名中带有Product的类都属于同一个主题,即同一产品簇。
  • Product通用产品接口。
  • Goods普通型产品接口,`GoodsImpl普通产品实现类。
  • ProductNum数字型产品接口,Product1Impl、`Product2Impl数字产品实现类。
  • ProductStr字母型产品接口,ProductAImplProductBImpl字母产品实现类。
  • ProductNumProductStr接口,继承了Product接口。

让我们根据上述定义带入到产品结构和产品簇的概念中:

  1. 只通过Product接口及其实现类观察:数字&字母产品实现类,它们同属于一个产品结构,一个产品簇。
  2. 通过命名特点观察:ProductNum、ProductStr的实现类分别属于两个产品结构,同属一个产品簇。Goods属于一个产品结构。

(二)抽象工厂模式

抽象工厂模式,又称为Kit模式,是一种创建型设计模式,其工厂内可生产的产品簇中的任意产品。

即,工厂可生产多个类似主题抽象产品的任意具体产品。换句话说,就是可获取多个产品接口的实例化对象。

它的核心思想与工厂方法模式相同,是工厂方法模式的改进和扩展。

  • 优点:

    • 可对产品簇本身进行约束管理,方便扩展替换产品簇。
    • 提供了统一创建一系列产品对象的方式,保持了产品簇的一致性。
  • 缺点

    • 新增新产品困难:除了扩展抽象产品接口外,还需要修改抽象工厂和所有具体工厂的实现。
    • 增加系统的复杂性和代码量。

二、模式原理

抽象工厂模式包含以下角色:

  1. 抽象产品(Abstract Product):声明创建一系列产品簇的方法,是抽象工厂模式的核心接口,直接与客户端进行交互。
  2. 具体产品(Concrete Product):实现了抽象工厂接口,负责创建一组具体的产品对象。
  3. 抽象工厂(Abstract Factory):定义产品的生产、业务方法,提供对外访问途径。
  4. 具体工厂(Concrete Factory):实现抽象工厂,负责创建具体产品的实例。

客户端直接与具体工厂进行交互,直接获取产品簇的实例,无需知道实例的创建过程。

产品抽象与具体之间的关系,如下图所示:

本次示例中,共有两个产品簇,分别是GoodsProduct接口。

  • Goods接口本身即是一个产品结构,有一个具体产品GoodsImpl实现类。
  • Product接口本身即是一个产品结构的抽象,它的具体产品是所有的实现类。
  • Product接口下有两个独立的产品结构,分别是ProductNum接口和ProductStr接口,它们分别又有自身的具体产品Product1ImplProduct2ImplProductAImplProductBImpl

工厂抽象与具体之间的关系,如下图所示:

本次示例中,共有GoodsAbstractFactoryProductAbstractFactory两个工厂,分别对应了GoodsProduct两个产品簇,它们都继承了一个基类工厂。

基类工厂的作用是提供共同方法或约束。

抽象工厂模式的工作原理如下:

  1. 客户端通过抽象工厂接口创建抽象产品对象,而无需直接实例化具体产品类。
  2. 具体工厂实现了抽象工厂接口,负责创建一组相关的具体产品对象。
  3. 客户端可以通过抽象工厂接口调用工厂的创建方法来获取所需的产品对象。不同的具体工厂实现会创建不同类型的产品对象。

三、应用实现

(一)应用场景

抽象工厂模式适用于:

  • 一个系统要独立于它的产品的创建、组合和表示时。
  • 需要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 提供一个产品类库,而只想显示它们的接口而不是实现时。
  • 适用于需要灵活地创建多个产品类和工厂类的情况。

(二)实现步骤

  1. 创建产品枚举类、产品分类枚举;
  2. 创建产品接口、产品抽象处理类、产品具体实现类;
  3. 创建抽象工厂、具体工厂;
  4. 应用。

(三)示例代码

关于产品的抽象与实现,仍采用工厂方法模式中的代码,但也新增代码:

现阶段产品分为字母类产品和数字类产品,新增了以下内容:

  • 新增数字型产品实现类。
  • 产品枚举中,增加数字型产品常量,且常量属性新增分类属性。
  • 新增产品分类枚举。

1. 产品枚举类

Java 复制代码
@Getter
@AllArgsConstructor
public enum ProductEnum {
    GOODS(-1, "普通产品", ProductCategoryEnum.NORMAL.getCode()),
    PRODUCT_A(0, "产品A", ProductCategoryEnum.STR.getCode()),
    PRODUCT_B(1, "产品B", ProductCategoryEnum.STR.getCode()),
    PRODUCT_1(2, "产品1", ProductCategoryEnum.NUM.getCode()),
    PRODUCT_2(3, "产品2", ProductCategoryEnum.NUM.getCode());

    private final int code;
    private final String msg;
    private final int category;

    /**
     * 根据code获取枚举
     *
     * @param code
     * @return
     */
    public static ProductEnum parseOfCode(int code) {
        return Arrays.stream(values())
                .filter(e -> e.getCode() == code)
                .findFirst()
                .orElse(null);
    }

    /**
     * 根据msg获取枚举
     *
     * @param msg
     * @return
     */
    public static ProductEnum parseOfMsg(String msg) {
        return Arrays.stream(values())
                .filter(e -> e.getMsg().equals(msg))
                .findFirst()
                .orElse(null);
    }

    /**
     * 获取所有的code
     *
     * @return
     */
    public static List<Integer> getAllCodes() {
        return Arrays.stream(values())
                .map(ProductEnum::getCode)
                .collect(Collectors.toList());
    }

    /**
     * 判断产品是否是数字型
     *
     * @param productEnum 产品枚举
     * @return true:是数字型产品
     */
    public static boolean isNum(ProductEnum productEnum) {
        return productEnum.getCategory() == ProductCategoryEnum.NUM.getCode();
    }

    /**
     * 判断产品是否是字母型
     *
     * @param productEnum 产品枚举
     * @return true:是字母型产品
     */
    public static boolean isStr(ProductEnum productEnum) {
        return productEnum.getCategory() == ProductCategoryEnum.STR.getCode();
    }
}

2. 产品分类枚举

Java 复制代码
@Getter
@AllArgsConstructor
public enum ProductCategoryEnum {
    NORMAL(-1, "普通型"),
    NUM(0, "数字型"),
    STR(1, "字母型");

    private final int code;
    private final String msg;
}

3. 产品簇接口及实现类

Goods产品簇接口:

Java 复制代码
public interface Goods {
    /**
     * 普通型产品特有方法
     * @param resolveContext
     * @param searchParams
     */
    List<ModelDto> normalGoods(ResolveContext resolveContext, SearchParams searchParams);
}

Goods产品簇具体实现类:

Java 复制代码
@Service
public class GoodsImpl implements Goods {
    @Override
    public List<ModelDto> normalGoods(ResolveContext resolveContext, SearchParams searchParams) {
        // 普通型产品特有方法
        System.out.println("productNormalImpl productNormal");
        return Lists.newArrayList();
    }
}

Product产品簇接口:

Java 复制代码
/**
 * Product基类接口
 */
public interface Product {
    /**
     * 获取产品枚举
     * @return 产品枚举
     */
    ProductEnum getProductEnum();

    /**
     * 展示产品
     */
    void show();

    /**
     * 产品功能
     * @return
     */
    List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams);
}

/**
 * 数字型产品
 */
public interface ProductNum extends Product {
    /**
     * 数字型产品特有方法
     * @param resolveContext
     * @param searchParams
     * @return
     */
    List<ModelDto> productNum(ResolveContext resolveContext, SearchParams searchParams);
}

/**
 * 字母型产品
 */
public interface ProductStr extends Product {
    /**
     * 字母型产品特有方法
     * @param resolveContext
     * @param searchParams
     * @return
     */
    List<ModelDto> productStr(ResolveContext resolveContext, SearchParams searchParams);
}

Product产品抽象处理类:

Java 复制代码
public abstract class AbsProductHandler implements Product {
    /**
     * 参数处理,转换为上下文
     * @param context 上下文
     * @param params  参数
     */
    protected void resolveParams(ResolveContext context, SearchParams params) {
        // 对参数进行处理,封装进context。这里只是简单的赋值,实际业务中可能会有更复杂的处理。
        context.setStrField(params.getStrParam());
        context.setNumField(params.getNumParam());
        context.setListField(params.getParamList());
        ModelReqDto modelReqDto = ModelReqDto.builder()
                .modelField(context.getStrField())
                .build();
        context.setInputDto(ResolveInputDto.builder()
                .modelReqDto(modelReqDto)
                .build());
    }

    /**
     * toModel
     * @param modelReqDto 请求参数
     * @return Model
     */
    protected Model toModel(ModelReqDto modelReqDto) {
        return Model.builder()
                .modelField(modelReqDto.getModelField())
                .build();
    }

    /**
     * 模拟查询并且进行业务处理
     * @param resolveContext 上下文
     * @return List<ModelRespDto>
     */
    protected List<ModelRespDto> selectList(ResolveContext resolveContext) {
        ResolveInputDto inputDto = resolveContext.getInputDto();
        ModelReqDto modelReqDto = inputDto.getModelReqDto();
        // 模拟查询数据库
        List<Model> modelList = Lists.newArrayList(toModel(modelReqDto));
        //进行业务处理
        // ......
        // return
        return BeanUtil.copyList(modelList, ModelRespDto.class);
    }
}

Product产品具体实现类:

Java 复制代码
/**
 * 数字型产品
 */

@Service
public class Product1Impl extends AbsProductHandler implements ProductNum {
    @Override
    public ProductEnum getProductEnum() {
        return ProductEnum.PRODUCT_1;
    }

    @Override
    public void show() {
        System.out.println("Product1Impl");
    }

    @Override
    public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
        // 构建上下文数据
        resolveParams(resolveContext, searchParams);
        // 具体业务处理
        List<ModelRespDto> modelRespDtoList = selectList(resolveContext);
        List<ModelDto> modelDtoList = BeanUtil.copyList(modelRespDtoList, ModelDto.class);
        System.out.println("Product1Impl function");
        // return
        return modelDtoList;
    }

    @Override
    public List<ModelDto> productNum(ResolveContext resolveContext, SearchParams searchParams) {
        // 数字型产品特有方法
        System.out.println("Product1Impl productNum");
        show();
        return function(resolveContext, searchParams);
    }
}

@Service
public class Product2Impl extends AbsProductHandler implements ProductNum {
    @Resource
    private ModelService modelService;

    @Override
    public ProductEnum getProductEnum() {
        return ProductEnum.PRODUCT_2;
    }

    @Override
    public void show() {
        System.out.println("Product2Impl");
    }

    @Override
    public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
        // 构建上下文数据
        resolveParams(resolveContext, searchParams);
        // 具体业务处理
        List<ModelDto> modelDtoList = modelService.list();
        System.out.println("Product2Impl function");
        // return
        return modelDtoList;
    }

    @Override
    public List<ModelDto> productNum(ResolveContext resolveContext, SearchParams searchParams) {
        // 数字型产品特有方法
        System.out.println("Product2Impl productNum");
        show();
        return function(resolveContext, searchParams);
    }
}

/* ==================================== */

/**
 * 字母型产品
 */

@Service
public class ProductAImpl extends AbsProductHandler implements ProductStr {
    @Override
    public ProductEnum getProductEnum() {
        return ProductEnum.PRODUCT_A;
    }

    @Override
    public void show() {
        System.out.println("ProductAImpl");
    }

    @Override
    public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
        // 构建上下文数据
        resolveParams(resolveContext, searchParams);
        // 具体业务处理
        List<ModelRespDto> modelRespDtoList = selectList(resolveContext);
        List<ModelDto> modelDtoList = BeanUtil.copyList(modelRespDtoList, ModelDto.class);
        System.out.println("ProductAImpl function");
        // return
        return modelDtoList;
    }

    @Override
    public List<ModelDto> productStr(ResolveContext resolveContext, SearchParams searchParams) {
        // 字母型产品特有方法
        System.out.println("ProductAImpl productStr");
        show();
        return function(resolveContext, searchParams);
    }
}

@Service
public class ProductBImpl extends AbsProductHandler implements ProductStr {
    @Resource
    private ModelService modelService;

    @Override
    public ProductEnum getProductEnum() {
        return ProductEnum.PRODUCT_B;
    }

    @Override
    public void show() {
        System.out.println("ProductBImpl");
    }

    @Override
    public List<ModelDto> function(ResolveContext resolveContext, SearchParams searchParams) {
        // 构建上下文数据
        resolveParams(resolveContext, searchParams);
        // 具体业务处理
        List<ModelDto> modelDtoList = modelService.list();
        System.out.println("ProductBImpl function");
        // return
        return modelDtoList;
    }

    @Override
    public List<ModelDto> productStr(ResolveContext resolveContext, SearchParams searchParams) {
        // 字母型产品特有方法
        System.out.println("ProductBImpl productStr");
        show();
        return function(resolveContext, searchParams);
    }
}

4. 抽象/具体工厂

抽象工厂

Java 复制代码
/**
 * 抽象工厂基类
 */
public interface Factory {
}

/**
 * Goods产品簇抽象工厂
 */
public interface GoodsAbstractFactory extends Factory {
    /**
     * 创建普通型产品
     *
     * @return
     */
    Goods createNormalGoods();
}

/**
 * Product产品簇抽象工厂
 */
public interface ProductAbstractFactory extends Factory {
    /**
     * 创建数字型产品
     *
     * @return
     */
    ProductNum createNumProduct(ProductEnum productEnum);

    /**
     * 创建全部数字型产品
     *
     * @return
     */
    List<ProductNum> createNumProductList();

    /**
     * 创建字母型产品
     * @return
     */
    ProductStr createStrProduct(ProductEnum productEnum);

    /**
     * 创建全部字母型产品
     * @return
     */
    List<ProductStr> createStrProductList();
}

具体工厂:

Java 复制代码
@Service
public class GoodsAbstractFactoryImpl implements GoodsAbstractFactory {
    @Resource
    private Goods goods;

    @Override
    public Goods createNormalGoods() {
        return goods;
    }
}

@Service
public class ProductAbstractFactoryImpl implements ProductAbstractFactory {
    @Resource
    private List<ProductNum> productNumList;
    @Resource
    private List<ProductStr> productStrList;

    @Override
    public ProductNum createNumProduct(ProductEnum productEnum) {
        return productNumList.stream()
                .filter(product -> ProductEnum.isNum(product.getProductEnum()))
                .filter(product -> product.getProductEnum().equals(productEnum))
                .findFirst()
                .orElse(null);
    }

    @Override
    public List<ProductNum> createNumProductList() {
        return productNumList.stream()
                .filter(product -> ProductEnum.isNum(product.getProductEnum()))
                .collect(Collectors.toList());
    }

    @Override
    public ProductStr createStrProduct(ProductEnum productEnum) {
        return productStrList.stream()
                .filter(product -> ProductEnum.isStr(product.getProductEnum()))
                .filter(product -> product.getProductEnum().equals(productEnum))
                .findFirst()
                .orElse(null);
    }

    @Override
    public List<ProductStr> createStrProductList() {
        return productStrList.stream()
                .filter(product -> ProductEnum.isStr(product.getProductEnum()))
                .collect(Collectors.toList());
    }
}

5. 应用

Java 复制代码
@Resource
private ProductAbstractFactory productAbstractFactory;
@Resource
private GoodsAbstractFactory goodsAbstractFactory;


@Test
public void abstractFactoryTest() {
    // 获取普通型型产品
    Goods normalGoods = goodsAbstractFactory.createNormalGoods();

    // 获取数字型产品
    ProductNum product1 = productAbstractFactory.createNumProduct(ProductEnum.PRODUCT_1);
    ProductNum product2 = productAbstractFactory.createNumProduct(ProductEnum.PRODUCT_2);
    // 获取所有数字型产品
    List<ProductNum> numProductList = productAbstractFactory.createNumProductList();

    // 获取字母型产品
    ProductStr productA = productAbstractFactory.createStrProduct(ProductEnum.PRODUCT_A);
    ProductStr productB = productAbstractFactory.createStrProduct(ProductEnum.PRODUCT_B);
    // 获取所有字母型产品
    List<ProductStr> strProductList = productAbstractFactory.createStrProductList();

    // 应用
    normalGoods.normalGoods(toResolveContext(), toSearchParams());
    product1.productNum(toResolveContext(), toSearchParams());
    product2.productNum(toResolveContext(), toSearchParams());
    productA.productStr(toResolveContext(), toSearchParams());
    productB.productStr(toResolveContext(), toSearchParams());
}

四、拓展延申

(一)误区

在学习的过程中,我误入过以下误区:

  1. 产品簇指的是实例会进行依赖引用?

    ❌,产品簇中的多个产品,它们之间的相关性是抽象的,可以有依赖关系,也可以只有名字有关系。是否相关,都是人为所决定的。

    但在生产中,两个抽象产品的相关,可以向上文一样定义一个父接口,来证明其的相关性。

  2. 工厂类每次只能创建一个产品实例?

    ❌,工厂类创建多少个产品实例,具体取决于设计和需求。

  3. 工厂类可以使用具体产品的工厂类生产产品?

    ❌,独立负责创建自己的产品对象,而不依赖于其他工厂。这样保证了工厂的独立性和灵活性,使得系统更具扩展性和可维护性。

  4. 一个工厂接口,可以对应多个产品簇,即可以有多个产品簇实现类?

    ❌,一个产品簇含有多个产品接口,每个产品簇都会对应一个工厂接口和一个工厂实现类。多个产品簇对应一个工厂接口,只会使多个产品之间紧耦合,灵活性、扩展性会变差。

  5. 多个工厂接口不能继承同一个基础工厂接口(BaseFactory​)?

    ❌,可以这么做,这样可以使得所有的工厂接口都具有一些共同的方法或约束。但是一个工厂接口不能有多个实现类,去生产多个产品簇。

(二)三种工厂模式

工厂模式有三类:简单工厂模式(①)、工厂方法模式(②)、抽象工厂模式(③)。

三者都属于创建型设计模式,都隐藏了对象创建的内部细节,可以根据需求灵活创建对象,解耦了客户端与具体产品之间的联系。

从多个切入点看三者的不同点:

角色:

  • 产品上,

    • ①②,都是单个产品结构(即一个抽象多个实现)。
    • ③,是多个产品结构组成的产品簇(即多个抽象每个都对应了多个实现)。
  • 工厂上,

    • ①,是一个工厂类,按需生产产品。
    • ②,是一个抽象工厂,多个具体工厂,每个具体工厂类负责创建一种具体产品。
    • ③,是一个抽象工厂,多个具体工厂,每个具体工厂类负责创建一组产品簇。

创建方式:

  • ①,根据请求创建相应产品,不能直接返回一个固定的产品。
  • ②,产品生产可按需或固定返回。
  • ③,可获取产品簇内的全部产品。

依赖关系

  • ①,依赖工厂类。
  • ②③,都依赖于抽象工厂和具体实现。

扩展性

  • ①,差,工厂类无法新增产品,新增需要修改工厂类代码。
  • ②,好,支持新增任意产品。
  • ③,较好,支持新增产品簇,无法对某个产品簇新增产品。

以上就是三种模式之间的异同。

三者的使用需要使情况而定,无论是哪种,都是为了降低程序耦合性,从而应对需求的变化。

参考资料

版权声明:个人学习记录,本博客所有文章均采用 CC-BY-NC-SA 许可协议。转载请注明出处。

若有侵权,请留言联系~

  1. 程杰 -《大话设计模式》- 第八章 - p.141-157
  2. 程序员进阶 -《设计模式 -工厂模式》
  3. java全栈知识体系 -《设计模式 创建型-抽象工厂(Abstract Factory)》
  4. 博客园 -《工厂模式,就这一篇搞定》
  5. CSDN -《抽象工厂模式》

如果您觉得文章对您有帮助,请点击文章正下方的小红心一下。您的鼓励是博主的最大动力!

相关推荐
kirito学长-Java14 分钟前
springboot/ssm考试系统Java学生在线考试系统web学习论坛源码
java·spring boot·学习
GGBondlctrl40 分钟前
【Spring MVC】初步了解Spring MVC的基本概念与如何与浏览器建立连接
java·spring boot·spring mvc·requestmapping·restcontroller
唐僧洗头爱飘柔95271 小时前
(Java并发编程——JUC)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
java·设计模式·并发编程·juc·reentrantlock·顺序控制·生产者与消费者
leeyayai_xixihah1 小时前
0-1实现SpringBoot项目开发(1)-SpringBoot+mybatis+mysql+Navicat
java·spring boot·spring
pingzhuyan1 小时前
EasyExcel: 结合springboot实现表格导出入(单/多sheet), 全字段校验,批次等操作(全)
java·spring boot·servlet·threadlocal·easyexcel
m0_748230212 小时前
【Spring Boot】Spring AOP中的环绕通知
spring boot·后端·spring
潘多编程2 小时前
动态定时任务在Spring Boot中集成Quartz的实践
java·spring boot·后端
2401_857636392 小时前
Spring Boot OA系统:企业办公自动化的创新实践
java·spring boot·后端
澄澈i2 小时前
设计模式学习[9]---模板方法模式
c++·学习·设计模式·模板方法模式
小菜日记^_^2 小时前
Maven高级篇
java·spring boot·后端·spring·maven·intellij-idea·mybatis