抽象工厂模式 - “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 -《抽象工厂模式》

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

相关推荐
Jabes.yang16 小时前
Java面试场景:从Spring Web到Kafka的音视频应用挑战
大数据·spring boot·kafka·spring security·java面试·spring webflux
ShareBeHappy_Qin18 小时前
Spring 中使用的设计模式
java·spring·设计模式
程序员小凯18 小时前
Spring Boot性能优化详解
spring boot·后端·性能优化
tuine19 小时前
SpringBoot使用LocalDate接收参数解析问题
java·spring boot·后端
番茄Salad20 小时前
Spring Boot项目中Maven引入依赖常见报错问题解决
spring boot·后端·maven
摇滚侠20 小时前
Spring Boot 3零基础教程,yml配置文件,笔记13
spring boot·redis·笔记
!if21 小时前
springboot mybatisplus 配置SQL日志,但是没有日志输出
spring boot·sql·mybatis
阿挥的编程日记21 小时前
基于SpringBoot的影评管理系统
java·spring boot·后端
java坤坤21 小时前
Spring Boot 集成 SpringDoc OpenAPI(Swagger)实战:从配置到接口文档落地
java·spring boot·后端
摇滚侠1 天前
Spring Boot 3零基础教程,整合Redis,笔记12
spring boot·redis·笔记