一、需求场景
将数据库中的数据进行处理然后使用excel进行导出。
二、数据特点
数据种类(产品族)分为指定几种,种类下面有不同的产品(类似不同工厂有不同的产品),每次触发接口都是使用某一产品作为实体返回。考虑到需求和数据格式决定使用抽象工厂设计模式完成此需求。
三、抽象工厂模式 (参考runoob.com)
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
主要解决:主要解决接口选择的问题
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。
关键代码:在一个工厂里聚合多个同类产品。
应用实例:工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景:
1、客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
2、强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码
3、提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现
注意事项:产品族难扩展,产品等级易扩展。
三、应用实例
1. AbstractFactory:
抽象工厂抽象基类,通过抽象基类获取指定工厂
public abstract class AbstractFactory {
/**
* 获取工厂A中的指定对象
*/
public abstract HotQuestion getHotQuestionImpl(String flag);
/**
* 获取工厂B中的指定对象
*/
public abstract ComplaintTwoRate getComplaintTwoRate(String flag);
}
2. FactoryProducer
创建一个工厂创造器/生成器类,通过传递要获取的工厂名称来获取工厂
@Component
@RequiredArgsConstructor
public class FactoryProducer {
private final ComplaintFactory complaintFactory;
private final HotQuestionFactory hotQuestionFactory;
public AbstractFactory getFactory(String choice) {
if ("ComplaintFactory".equalsIgnoreCase(choice)) {
return complaintFactory;
} else if ("HotQuestionFactory".equalsIgnoreCase(choice)) {
return hotQuestionFactory;
}
return null;
}
}
3. ComplaintFactory
工厂A,在此根据产品名称从工厂中获取指定的产品
@Component
@RequiredArgsConstructor
public class ComplaintFactory extends AbstractFactory {
private final ComplaintTwoRateAndTwoRatioCity complaintAndTwoRatioCity;
private final ComplaintTwoRateAndTwoRatioArea complaintAndTwoRatioArea;
@Override
public ComplaintTwoRate getComplaintTwoRate(String flag) {
if ("city".equalsIgnoreCase(flag)) {
return complaintAndTwoRatioCity;
} else if ("area".equalsIgnoreCase(flag)) {
return complaintAndTwoRatioArea;
} else if ("street".equalsIgnoreCase(flag)) {
return complaintAndTwoRatioCity;
}
return null;
}
@Override
public HotQuestion getHotQuestionImpl(String flag) {
return null;
}
}
4. ComplaintTwoRate
为工厂A的产品创建一个接口。此类代表所有工厂A的产品,用于方法函数返回对象。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
public interface ComplaintTwoRate {
/**
* 从数据库获取数据
*/
List<Object> getWriteData(ExportRequest request);
/**
* 将别名和数填充到BigExcelWriter中(一个sheet页数据)
*/
void addAliasAndData(BigExcelWriter writer, List<Object> tList);
}
5. ComplaintTwoRateAndTwoRatioCity
工厂A的产品A。
@Component
@RequiredArgsConstructor
public class ComplaintTwoRateAndTwoRatioCity implements ComplaintTwoRate {
private final IExportExcelService exportExcelService;
private final ExportExcelUtil<Object> exportExcelUtil;
@Override
public List<Object> getWriteData(ExportRequest request) {
return new ArrayList<>(exportExcelService.getGoverComplaintTwoRateCityList(request));
}
@Override
public void addAliasAndData(BigExcelWriter writer, List<Object> tList) {
...
}
}
6. ComplaintTwoRateAndTwoRatioArea
工厂A的产品B。
@Component
@RequiredArgsConstructor
public class ComplaintTwoRateAndTwoRatioArea implements ComplaintTwoRate {
private final ExportExcelUtil<Object> exportExcelUtil;
@Override
public List<Object> getWriteData(ExportRequest request) {
return null;
}
@Override
public void addAliasAndData(BigExcelWriter writer, List<Object> tList) {
...
}
}
7. HotQuestionFactory
工厂B,在此根据产品名称从工厂中获取指定的产品
@Component
@RequiredArgsConstructor
public class HotQuestionFactory extends AbstractFactory {
private final HotQuestionImplCity hotQuestionImplA;
@Override
public ComplaintTwoRate getComplaintTwoRate(String flag) {
return null;
}
@Override
public HotQuestion getHotQuestionImpl(String flag) {
if ("A".equalsIgnoreCase(flag)) {
return hotQuestionImplA;
} else if ("B".equalsIgnoreCase(flag)) {
return hotQuestionImplA;
} else if ("C".equalsIgnoreCase(flag)) {
return hotQuestionImplA;
}
return null;
}
}
8. HotQuestion
为工厂B的产品创建一个接口。此类代表所有工厂B的产品,用于方法函数返回对象。(作用同ComplaintTwoRate)
public interface HotQuestion {
/**
* 从数据库获取数据
*/
List<Object> getWriteData(ExportRequest request);
/**
* 将别名和数填充到BigExcelWriter中(一个sheet页数据)
*/
void addAliasAndData(BigExcelWriter writer, List<Object> tList);
}
9. HotQuestionImplCity
工厂B的产品C
@Component
@RequiredArgsConstructor
public class HotQuestionImplCity implements HotQuestion {
private final ExportExcelUtil<Object> exportExcelUtil;
@Override
public List<Object> getWriteData(ExportRequest request) {
return null;
}
@Override
public void addAliasAndData(BigExcelWriter writer, List<Object> tList) {
...
}
}
10. HotQuestionImplArea
工厂C的产品D
@Component
@RequiredArgsConstructor
public class HotQuestionImplArea implements HotQuestion {
private final ExportExcelUtil<Object> exportExcelUtil;
@Override
public List<Object> getWriteData(ExportRequest request) {
return null;
}
@Override
public void addAliasAndData(BigExcelWriter writer, List<Object> tList) {
...
}
}
11. FileExportController
使用 FileExportController 来获取 AbstractFactory,通过传递名称来获取实体类的对象。
//获取工厂
AbstractFactory fileExportFactory = factoryProducer.getFactory("ComplaintFactory");
//获取产品
ComplaintTwoRate complaintTwoRateCity = fileExportFactory.getComplaintTwoRate("city");
// 调用方法-填充字段名和数据
complaintTwoRateCity.addAliasAndData(...);
四、总结
1. 根据需求和数据规律确定使用哪种设计模式,该情景属于在创建对象的时候不同其他操作(导出excel)相同,所以需要的是创建型设计模式,考虑数据规律可以使用抽象工厂设计模式
2. 为什么不使用工厂模式而使用抽象工厂模式?
简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改。
抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类,抽象工厂模式较好的实现了“开放-封闭”原则,是三个模式中较为抽象,并具一般性的模式。
本例中数据种类为产品族,某一数据为产品。
综上抽象工厂模式更合适此情景。
五、所遇问题
- 类比较多容易混乱,所以对类名设计严格遵守阿里编码规范中的对类名涉及要求:
【推荐】如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。