深入解析:电商数据导出实战:用工厂模式消除重复代码设计

引言:一个常见的电商开发痛点

最近在一个电商项目上上了一课,遇到过这样一个需求:平台需要为商家提供多种数据导出功能,包括商品数据、订单数据、会员数据、销售统计等,每种数据又要支持多种格式:Excel2003、Excel2007、CSV、PDF,甚至还要支持腾讯云COS直接上传。

最初的实现简单粗暴:

public class DataExportService {
    public void exportProductData(String fileType, List products) {
        if ("excel2003".equals(fileType)) {
            // 导出Excel2003格式的商品数据
            Excel2003Export export = new Excel2003Export();
            export.exportProducts(products);
        } else if ("excel2007".equals(fileType)) {
            // 导出Excel2007格式的商品数据
            Excel2007Export export = new Excel2007Export();
            export.exportProducts(products);
        } else if ("csv".equals(fileType)) {
            // 导出CSV格式的商品数据
            CSVExport export = new CSVExport();
            export.exportProducts(products);
        }
        // ... 更多if-else
    }
    public void exportOrderData(String fileType, List orders) {
        // 类似的重复代码...
    }
}

问题很明显:每当新增一种导出格式或导出数据类型时,我都要在各个方法中添加新的if-else分支,也就是我们常见的屎山之一,这样子代码重复严重,维护困难,违反了开闭原则(对扩展开放,对修改关闭)。

一、工厂模式简介:从现实工厂到代码设计

在深入解决方案前,我们先理解工厂模式的核心思想。工厂模式是一种创建型设计模式,它提供了一种封装对象创建过程的方式,将对象的实例化过程延迟到子类。

1.1 工厂模式的三种形态

工厂模式主要分为三种类型:

  • 简单工厂模式:一个工厂类根据参数的不同返回不同的产品实例

  • 工厂方法模式:定义一个创建对象的接口,但让子类决定实例化哪个类

  • 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

1.2 工厂模式的优点

欸,在我的深入学习之下,我发现了工厂模式在电商系统中带来以下好处:

  1. 封装变化:创建逻辑有可能变化,封装成工厂类后,对调用者透明

  2. 代码复用:抽离创建代码到独立工厂类,避免重复劳动

  3. 隔离复杂性:封装复杂创建逻辑,调用者无需了解对象创建细节

  4. 控制复杂度:让原本的类职责更单一,代码更简洁

二、实战:用简单工厂模式重构数据导出

我们先从最简单的简单工厂模式开始重构代码。

2.1 定义统一的导出接口

public interface DataExporter {
    /**
     * 导出数据到指定路径
     */
    void export(List data, String filePath);
    /**
     * 导出数据并上传到腾讯云COS
     */
    String exportToCloud(List data, String cloudPath);
    /**
     * 支持的文件类型
     */
    FileType supportsType();
}
public enum FileType {
    EXCEL_2003, EXCEL_2007, CSV, PDF
}

2.2 实现具体导出器

@Component
public class Excel2003Exporter implements DataExporter {
    private static final Logger logger = LoggerFactory.getLogger(Excel2003Exporter.class);
    @Autowired
    private TencentCloudService cloudService;
    @Override
    public void export(List data, String filePath) {
        // 使用Apache POI实现Excel2003导出逻辑
        logger.info("导出Excel2003文件,路径:{},数据量:{}", filePath, data.size());
        // 具体实现...
    }
    @Override
    public String exportToCloud(List data, String cloudPath) {
        // 先导出到临时文件
        String tempPath = "/tmp/export_" + System.currentTimeMillis() + ".xls";
        export(data, tempPath);
        // 上传到腾讯云COS
        String url = cloudService.uploadToCOS(tempPath, cloudPath);
        logger.info("文件已上传到腾讯云COS:{}", url);
        // 删除临时文件
        new File(tempPath).delete();
        return url;
    }
    @Override
    public FileType supportsType() {
        return FileType.EXCEL_2003;
    }
}

2.3 创建简单工厂

@Component
public class SimpleExporterFactory {
    @Autowired
    private List exporters;
    private Map exporterMap;
    @PostConstruct
    public void init() {
        exporterMap = exporters.stream()
            .collect(Collectors.toMap(DataExporter::supportsType, Function.identity()));
    }
    public DataExporter createExporter(FileType fileType) {
        DataExporter exporter = exporterMap.get(fileType);
        if (exporter == null) {
            throw new IllegalArgumentException("不支持的导出类型:" + fileType);
        }
        return exporter;
    }
}

2.4 使用工厂的统一导出服务

@Service
public class UnifiedExportService {
    @Autowired
    private SimpleExporterFactory exporterFactory;
    public String exportData(List data, FileType fileType, boolean uploadToCloud) {
        DataExporter exporter = exporterFactory.createExporter(fileType);
        if (uploadToCloud) {
            String cloudPath = generateCloudPath(fileType);
            return exporter.exportToCloud(data, cloudPath);
        } else {
            String localPath = generateLocalPath(fileType);
            exporter.export(data, localPath);
            return localPath;
        }
    }
    private String generateCloudPath(FileType fileType) {
        return "export/" + LocalDate.now().toString() + "/" +
               System.currentTimeMillis() + getFileExtension(fileType);
    }
    private String generateLocalPath(FileType fileType) {
        // 生成本地文件路径
        return "/data/export/" + System.currentTimeMillis() + getFileExtension(fileType);
    }
    private String getFileExtension(FileType fileType) {
        // 根据文件类型返回对应扩展名
        switch (fileType) {
            case EXCEL_2003: return ".xls";
            case EXCEL_2007: return ".xlsx";
            case CSV: return ".csv";
            case PDF: return ".pdf";
            default: return ".bin";
        }
    }
}

这样重构后,我们的代码从重复的屎山if-else中解放出来,每当需要新增导出格式时,只需要实现DataExporter接口并声明为Spring组件即可,无需修改现有代码。

三、进阶:使用工厂方法模式支持复杂场景

随着业务发展,简单的导出需求变得复杂:我们需要支持按数据类型差异化导出,比如商品数据、订单数据的Excel导出格式完全不同。

3.1 定义工厂方法接口

public interface ExportFactory {
    /**
     * 创建数据导出器
     */
    DataExporter createExporter();
    /**
     * 创建数据转换器(不同数据类型转换逻辑不同)
     */
    DataConverter createConverter();
    /**
     * 创建数据校验器
     */
    DataValidator createValidator();
}
public interface DataConverter {
    Object convert(Object data);
}
public interface DataValidator {
    boolean validate(List data);
}

3.2 实现具体工厂

@Component
public class ProductExportFactory implements ExportFactory {
    @Autowired
    private TencentCloudService cloudService;
    @Override
    public DataExporter createExporter() {
        ProductDataExporter exporter = new ProductDataExporter();
        exporter.setCloudService(cloudService);
        return exporter;
    }
    @Override
    public DataConverter createConverter() {
        return new ProductDataConverter();
    }
    @Override
    public DataValidator createValidator() {
        return new ProductDataValidator();
    }
}
@Component
public class OrderExportFactory implements ExportFactory {
    @Autowired
    private TencentCloudService cloudService;
    @Override
    public DataExporter createExporter() {
        OrderDataExporter exporter = new OrderDataExporter();
        exporter.setCloudService(cloudService);
        return exporter;
    }
    @Override
    public DataConverter createConverter() {
        return new OrderDataConverter();
    }
    @Override
    public DataValidator createValidator() {
        return new OrderDataValidator();
    }
}

3.3 工厂的工厂:使用抽象工厂管理复杂对象簇

当导出逻辑变得更加复杂,需要创建一系列相互依赖的对象时,我们可以使用抽象工厂模式。

public interface DataExportAbstractFactory {
    ExportFactory getExportFactory(DataType dataType);
}
@Component
public class DataExportAbstractFactoryImpl implements DataExportAbstractFactory {
    @Autowired
    private Map factoryMap;
    @Override
    public ExportFactory getExportFactory(DataType dataType) {
        String beanName = dataType.name().toLowerCase() + "ExportFactory";
        ExportFactory factory = factoryMap.get(beanName);
        if (factory == null) {
            throw new IllegalArgumentException("不支持的数据类型:" + dataType);
        }
        return factory;
    }
}

四、配合策略模式进一步优化

在实际项目中,工厂模式经常与策略模式配合使用,进一步优化条件判断语句。

4.1 定义策略接口

public interface ExportStrategy {
    /**
     * 处理导出请求
     */
    ExportResult export(ExportRequest request);
    /**
     * 支持的导出类型
     */
    boolean supports(ExportType exportType);
}
public class ExportRequest {
    private DataType dataType;
    private FileType fileType;
    private List data;
    private boolean uploadToCloud;
    // 其他参数...
}

4.2 实现策略与工厂的配合

@Service
public class DataExportService {
    @Autowired
    private List strategies;
    @Autowired
    private DataExportAbstractFactory abstractFactory;
    public ExportResult handleExport(ExportRequest request) {
        // 1. 选择策略
        ExportStrategy strategy = strategies.stream()
            .filter(s -> s.supports(request.getExportType()))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("不支持的导出类型"));
        // 2. 使用策略处理导出
        return strategy.export(request);
    }
    public ExportResult exportWithFactory(ExportRequest request) {
        // 1. 通过抽象工厂获取具体工厂
        ExportFactory factory = abstractFactory.getExportFactory(request.getDataType());
        // 2. 使用工厂创建相关对象
        DataValidator validator = factory.createValidator();
        DataConverter converter = factory.createConverter();
        DataExporter exporter = factory.createExporter();
        // 3. 执行导出流程
        if (!validator.validate(request.getData())) {
            throw new IllegalArgumentException("数据校验失败");
        }
        Object convertedData = converter.convert(request.getData());
        if (request.isUploadToCloud()) {
            String url = exporter.exportToCloud((List) convertedData,
                generateCloudPath(request.getFileType()));
            return ExportResult.success(url);
        } else {
            String path = exporter.export((List) convertedData,
                generateLocalPath(request.getFileType()));
            return ExportResult.success(path);
        }
    }
}

五、总结与最佳实践

工厂模式重构电商数据导出模块,既然我们用了,那肯定是能够偷懒的啊,我们简单总结一下工厂模式的小小优点。

5.1 架构改善点

  1. 遵守开闭原则:新增导出格式无需修改现有代码

  2. 单一职责:每个类只负责特定类型的导出

  3. 代码复用:通用导出逻辑在基类中实现

  4. 易于测试:可以轻松Mock导出器进行单元测试

5.2 性能优化实践

在大数据量导出场景下,我还总结了一些优化经验:

@Service
public class OptimizedExportService {
    public void exportLargeData(List data, FileType fileType) {
        // 1. 分批次处理,避免内存溢出
        int batchSize = 1000;
        List> batches = Lists.partition(data, batchSize);
        // 2. 使用工厂创建导出器
        DataExporter exporter = exporterFactory.createExporter(fileType);
        // 3. 并行处理提高性能
        batches.parallelStream().forEach(batch -> {
            String tempPath = generateTempPath();
            exporter.export(batch, tempPath);
            // 合并文件...
        });
    }
}

5.3 何时使用工厂模式?

根据多年经验,我总结出以下使用场景:

  • 对象创建逻辑复杂:当创建对象需要复杂初始化逻辑时

  • 需要统一管理对象生命周期:当需要集中控制对象创建过程时

  • 系统需要良好扩展性:当预期会有多种类似产品加入时

  • 需要降低代码耦合度:当希望将使用者和具体实现解耦时

5.4 设计模式陷阱之过度设计

工厂模式虽好,但也要避免过度设计,有些简单的逻辑直接简单暴力就好,没必要秀肌肉,写一个设计模式,简而言之就是设计模式虽好,可不要贪杯哟:

  1. 不要过早抽象:如果只有2-3种具体产品,简单工厂可能更合适

  2. 避免工厂泛滥:太多工厂类会增加系统复杂度

  3. 注意循环依赖:在Spring环境中特别注意工厂间的循环依赖

结语

工厂模式是电商系统中极其重要的设计模式,恰当使用可以大幅提升代码的可维护性和扩展性。在实际项目中,从简单工厂开始,随着业务复杂度的增加,逐步升级到工厂方法模式或抽象工厂模式。

记住:设计模式是手段,不是目的。我们的目标是写出优雅、可维护的代码,而不是为了使用模式而使用模式。

posted on 2025-12-17 18:32  ljbguanli  阅读(5)  评论(0)    收藏  举报