深入解析:电商数据导出实战:用工厂模式消除重复代码设计
引言:一个常见的电商开发痛点
最近在一个电商项目上上了一课,遇到过这样一个需求:平台需要为商家提供多种数据导出功能,包括商品数据、订单数据、会员数据、销售统计等,每种数据又要支持多种格式: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 工厂模式的优点
欸,在我的深入学习之下,我发现了工厂模式在电商系统中带来以下好处:
封装变化:创建逻辑有可能变化,封装成工厂类后,对调用者透明
代码复用:抽离创建代码到独立工厂类,避免重复劳动
隔离复杂性:封装复杂创建逻辑,调用者无需了解对象创建细节
控制复杂度:让原本的类职责更单一,代码更简洁
二、实战:用简单工厂模式重构数据导出
我们先从最简单的简单工厂模式开始重构代码。
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 架构改善点
遵守开闭原则:新增导出格式无需修改现有代码
单一职责:每个类只负责特定类型的导出
代码复用:通用导出逻辑在基类中实现
易于测试:可以轻松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 设计模式陷阱之过度设计
工厂模式虽好,但也要避免过度设计,有些简单的逻辑直接简单暴力就好,没必要秀肌肉,写一个设计模式,简而言之就是设计模式虽好,可不要贪杯哟:
不要过早抽象:如果只有2-3种具体产品,简单工厂可能更合适
避免工厂泛滥:太多工厂类会增加系统复杂度
注意循环依赖:在Spring环境中特别注意工厂间的循环依赖
结语
工厂模式是电商系统中极其重要的设计模式,恰当使用可以大幅提升代码的可维护性和扩展性。在实际项目中,从简单工厂开始,随着业务复杂度的增加,逐步升级到工厂方法模式或抽象工厂模式。
记住:设计模式是手段,不是目的。我们的目标是写出优雅、可维护的代码,而不是为了使用模式而使用模式。
浙公网安备 33010602011771号