-
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,它提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。
-
工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。
-
通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。
设计模式,最近持续更新中,如需要请关注
如果你觉得我分享的内容或者我的努力对你有帮助,或者你只是想表达对我的支持和鼓励,请考虑给我点赞、评论、收藏。您的鼓励是我前进的动力,让我感到非常感激。
文章目录
- 1 概要
- 2 实现
- 3 Demo代码
- 4 开发案例
- 4.1 算法执行服务里,不同的任务数据来源不同,执行脚本类型不同,结果处理不同,使用工厂模式
- 4.2 网络还原时,不同的采集数据,解析处理方式不同
1 概要
意图
定义一个创建对象的接口,让其子类决定实例化哪一个具体的类。工厂模式使对象的创建过程延迟到子类。
主要解决
接口选择的问题。
何时使用
当我们需要在不同条件下创建不同实例时。
如何解决
通过让子类实现工厂接口,返回一个抽象的产品。
关键代码
对象的创建过程在子类中实现。
应用实例
- 汽车制造:你需要一辆汽车,只需从工厂提货,而不需要关心汽车的制造过程及其内部实现。
- Hibernate:更换数据库时,只需更改方言(Dialect)和数据库驱动(Driver),即可实现对不同数据库的切换。
优点
- 调用者只需要知道对象的名称即可创建对象。
- 扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。
- 屏蔽了产品的具体实现,调用者只关心产品的接口。
缺点
每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。
使用场景
- 日志记录:日志可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志的位置。
- 数据库访问:当用户不知道最终系统使用哪种数据库,或者数据库可能变化时。
- 连接服务器的框架设计:需要支持 “POP3”、“IMAP”、“HTTP” 三种协议,可以将这三种协议作为产品类,共同实现一个接口。
- 在算法执行服务中,每个任务需要处理的数据来源不同,根据数据类型创建对应的数据出来handle类,不用关心handle里的内部处理逻辑
注意事项
工厂模式适用于生成复杂对象的场景。如果对象较为简单,通过 new 即可完成创建,则不必使用工厂模式。使用工厂模式会引入一个工厂类,增加系统复杂度。
结构
工厂模式包含以下几个主要角色:
- 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
- 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。
2 实现
我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。
FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
3 Demo代码
Shape
/*** 公共接口*/
public interface Shape {void draw();
}
Circle
/*** 圆形,形状的实现类*/
public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");}
}
Rectangle
/*** 矩形,形状的实现类*/
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");}
}
Square
/*** 方形,形状的实现类*/
public class Square implements Shape {@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");}
}
ShapeFactory
/*** 形状工厂类【根据不同的参数创建对应的实例】*/
public class ShapeFactory {//使用 getShape 方法获取形状类型的对象public Shape getShape(String shapeType) {if (shapeType == null) {return null;}if (shapeType.equalsIgnoreCase("CIRCLE")) {return new Circle();} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {return new Rectangle();} else if (shapeType.equalsIgnoreCase("SQUARE")) {return new Square();}return null;}
}
FactoryPatternDemo
/*** 总结:用来根据不同的参数创建对象*/
public class FactoryPatternDemo {public static void main(String[] args) {ShapeFactory shapeFactory = new ShapeFactory();//获取 Circle 的对象,并调用它的 draw 方法Shape shape1 = shapeFactory.getShape("CIRCLE");//调用 Circle 的 draw 方法shape1.draw();//获取 Rectangle 的对象,并调用它的 draw 方法Shape shape2 = shapeFactory.getShape("RECTANGLE");//调用 Rectangle 的 draw 方法shape2.draw();//获取 Square 的对象,并调用它的 draw 方法Shape shape3 = shapeFactory.getShape("SQUARE");//调用 Square 的 draw 方法shape3.draw();}
}
效果
4 开发案例
4.1 算法执行服务里,不同的任务数据来源不同,执行脚本类型不同,结果处理不同,使用工厂模式
说明:
- TaskAbstractFactory命名成抽象工厂,可以创建出来DataSetHandle工厂,ScriptExecuteHandle工厂,ResultHandle工厂,但是实现时,时间关系,简化成简单工厂模式。
- 工厂返回的对象,每次都是新创建出来的,因为这些handle每次创建初始化的参数是不同的,和下面的第二个案例有所不同
工厂类
/*** 任务抽象工厂类,创建各种处理类* @since 2023 -10-08 16:13*/
public class TaskAbstractFactory {private static final Logger LOGGER = LoggerFactory.getLogger(TaskAbstractFactory.class);/*** Gets script execute handle.** @param scriptParam the script param* @param algoId the algo id* @return the script execute handle*/public static ScriptExecuteHandle getScriptExecuteHandle(ScriptParam scriptParam, String algoId) {if (Constants.ScriptType.PYTHON.equals(scriptParam.getScriptType()) || Constants.ScriptType.PYTHON3.equals(scriptParam.getScriptType())) {return new PythonScriptExecuteHandle(scriptParam);} else {LOGGER.error("The algorithm type is not supported. algoId: {} ,scriptType: {} ", algoId,scriptParam.getScriptType());throw new CommonServiceException(AIModelError.ALGO_TYPE_NOT_SUPPORTED);}}/*** Gets data set handle list.** @param dataSets the data sets* @return the data set handle list*/public static List<DataSetHandle> getDataSetHandleList(List<InputDataSource> dataSets) {ArrayList<DataSetHandle> dataSetHandleList = new ArrayList<>(10);for (InputDataSource inputDataSource : dataSets) {dataSetHandleList.add(getDataSetHandle(inputDataSource));}return dataSetHandleList;}/*** Gets data set handle.** @param inputDataSource the input data source* @return the data set handle*/public static DataSetHandle getDataSetHandle(InputDataSource inputDataSource) {if (Constants.DataSourceType.MINIO_FILE.equalsIgnoreCase(inputDataSource.getDatatype())) {DataSetHandle dataSetHandle = new S3DataSetHandle(inputDataSource.getDataSourceInfo(),inputDataSource.getDataSourceConfig());// 进行数据源校验dataSetHandle.checkDataSource();return dataSetHandle;} else if (Constants.DataSourceType.HDFS_FILE.equalsIgnoreCase(inputDataSource.getDatatype())) {DataSetHandle dataSetHandle = new HdfsDataSetHandle(inputDataSource.getDataSourceInfo(),inputDataSource.getDataSourceConfig());// 进行数据源校验dataSetHandle.checkDataSource();return dataSetHandle;} else {LOGGER.error("The data source type is not supported. datatype: {} ", inputDataSource.getDatatype());throw new CommonServiceException(AIModelError.DATA_TYPE_NOT_SUPPORTED);}}/*** Gets result handle.** @param calculateParam the calculate param* @param scriptParam the script param* @return the result handle*/public static ResultHandle getResultHandle(CalculateParam calculateParam, ScriptParam scriptParam) {// 【预留】 设置结果集存放的数据源。目前所有的算法都有数据集,取数据集的第一个数据源作为结果上传的数据源OutputDataSource outputDataSource = getOutDataSource(calculateParam.getDataSets().get(0));if (Constants.CustomizedAlgoId.POTENTIAL_GUEST_ALGO_ID.equals(calculateParam.getAlgoId())) {// 定制化的处理方式,需要走特有的处理方式。此类算法,只能提前预置后return new PotentialGuestResultHandle(scriptParam, outputDataSource);} else {// 任务结果走默认处理方式,此类算法可以通过算法管理界面可以添加return new ResultHandle(scriptParam, outputDataSource);}}private static OutputDataSource getOutDataSource(InputDataSource inputDataSource) {return new OutputDataSource(inputDataSource.getDatatype(), inputDataSource.getDataSourceConfig());}}
使用
public TaskEntity(TaskInfo taskInfo, CalculateParam calculateParam, String algoPackDir, String localFileDir) {this.scriptParam = getScriptParam(calculateParam, algoPackDir, localFileDir);this.taskInfo = taskInfo;this.shellParamHandle = TaskAbstractFactory.getScriptExecuteHandle(scriptParam, calculateParam.getAlgoId());this.dataSetHandleList = TaskAbstractFactory.getDataSetHandleList(calculateParam.getDataSets());this.resultHandle = TaskAbstractFactory.getResultHandle(calculateParam, scriptParam);}
其他说明
设计是工厂创建实例的是子类,返回的是父类。多态的体现。同事父类有默认方法,子类是对父类的扩展, 或者重写。如下:
DataSetHandle
/*** 数据集处理父类* @since 2023 -09-15 15:35*/
public abstract class DataSetHandle {private static final Logger LOGGER = LoggerFactory.getLogger(DataSetHandle.class);/*** The Data source info.*/protected String dataSourceInfo;/*** The Data source config.*/protected String dataSourceConfig;/*** Instantiates a new Data set handle.** @param dataSourceInfo the data source info* @param dataSourceConfig the data source config*/public DataSetHandle(String dataSourceInfo, String dataSourceConfig) {this.dataSourceInfo = dataSourceInfo;this.dataSourceConfig = dataSourceConfig;}/*** Check data source.*/public void checkDataSource() {// 对参数的json格式进行校验if (!MyStringUtil.checkJson(dataSourceInfo)) {LOGGER.error("dataSourceInfo json format error.");throw new CommonServiceException(AIModelError.PARAM_ERROR, "dataSourceInfo json format error.");}if (StringUtils.isNotEmpty(dataSourceConfig) && !MyStringUtil.checkJson(dataSourceConfig)) {LOGGER.error("dataSourceConfig json format error.");throw new CommonServiceException(AIModelError.PARAM_ERROR, "dataSourceConfig json format error.");}}/*** Handle data.** @param taskId the task id* @param localFileDir the local file dir*/public abstract void handleData(String taskId, String localFileDir);}
S3DataSetHandle
/*** S3或者minio类型的数据集处理类* @since 2023 -09-15 15:35*/
public class S3DataSetHandle extends DataSetHandle {private static final Logger LOGGER = LoggerFactory.getLogger(S3DataSetHandle.class);/*** Instantiates a new S 3 data set handle.** @param dataSourceInfo the data source info* @param dataSourceConfig the data source config*/public S3DataSetHandle(String dataSourceInfo, String dataSourceConfig) {super(dataSourceInfo, dataSourceConfig);}/*** Check data source.*/@Overridepublic void checkDataSource() {// 1 父类进行参数json格式的校验super.checkDataSource();// 2 具体子类,进行特性校验List<S3DataSourceInfo> s3DataSourceList = JSON.parseArray(dataSourceInfo, S3DataSourceInfo.class);for (S3DataSourceInfo s3DataSource : s3DataSourceList) {// 目前S3,仅支持zip文件if (!Constants.FileType.ZIP.equalsIgnoreCase(s3DataSource.getFileType())) {LOGGER.error("The file type is not supported. fileType:{}", s3DataSource.getFileType());throw new CommonServiceException(AIModelError.PARAM_ERROR,"The file type is not supported. fileType: " + s3DataSource.getFileType());}if (StringUtils.isEmpty(s3DataSource.getFileId()) || StringUtils.isEmpty(s3DataSource.getStoreDirName())) {LOGGER.error("fileId and storeDirName cannot be empty.");throw new CommonServiceException(AIModelError.PARAM_ERROR, "fileId and storeDirName cannot be empty.");}}}/*** Handle data.** @param taskId the task id* @param localFileDir the local file dir*/@Overridepublic void handleData(String taskId, String localFileDir) {// 1 获取配置S3DataSourceConfig s3DataSourceConfig = JSON.parseObject(dataSourceConfig, S3DataSourceConfig.class);// 2 初始化S3客户端S3ClientUtils s3ClientUtils = S3ClientUtils.getInstance(s3DataSourceConfig);for (S3DataSourceInfo s3DataSourceInfo : JSON.parseArray(dataSourceInfo, S3DataSourceInfo.class)) {InputStream s3InputStream = null;try {// 3 获取数据流s3InputStream = s3ClientUtils.download(s3DataSourceConfig.getBucketName(),s3DataSourceInfo.getFileId());// 4 将文件保存在本地磁盘saveFileToLocal(s3InputStream, s3DataSourceInfo, taskId, localFileDir + taskId);} finally {MyIOUtils.closeInputStream(s3InputStream);}}}
}
4.2 网络还原时,不同的采集数据,解析处理方式不同
说明:
- 创建对象由spring类管理,创建出来的对象是单例的,这个案例1有所不同,案例1,每个类初始化的参数不同。这个案例对象初始化方式一样,只是处理逻辑不同。
- 在vimpim对象是,因为参数不同所有每次都需要new
工厂类:
/*** The type Ods process factory.** @since 2024 -06-11 10:54*/
@Slf4j
@Service
public class OdsProcessFactory {private static final List<VimPimModelDto> VIM_PIM_MODEL_V2_LIST = new ArrayList<>();private static final String VIM_PIM_MODEL_V2_FILE_PATH = "2.json";private static final List<String> VIM_PIM_MODEL_V2_WHITE_LIST = Arrays.asList();private static final List<VimPimModelDto> VIM_PIM_MODEL_V3_LIST = new ArrayList<>();private static final String VIM_PIM_MODEL_V3_FILE_PATH = "3.json";private static final List<String> VIM_PIM_MODEL_V3_WHITE_LIST = Arrays.asList();/*** The Data source.*/@Resource(name = "gauss")DataSource dataSource;@Autowiredprivate VimPimRepository vimPimRepository;@Autowiredprivate VnfMaeCnDataProcessDomainService vnfMaeCnDataProcessDomainService;@Autowiredprivate VnflocLcmDataProcessDomainService vnflocLcmDataProcessDomainService;static {// 初始化模型数据initModel(VIM_PIM_MODEL_V2_FILE_PATH, VIM_PIM_MODEL_V2_WHITE_LIST, VIM_PIM_MODEL_V2_LIST);initModel(VIM_PIM_MODEL_V3_FILE_PATH, VIM_PIM_MODEL_V3_WHITE_LIST, VIM_PIM_MODEL_V3_LIST);}private static void initModel(String vimPimModelFilePath, List<String> vimPimModelWhiteList,List<VimPimModelDto> vimPimModelList) {}/*** Gets ods process service.** @param taskDto the task dto* @param dataFormat the data format* @return the ods process service*/public DataProcessDomainService getOdsProcessService(CollectionTaskDto taskDto, int dataFormat) {if (OdsParseConstants.OdsDataFormat.VNF_MAECN == dataFormat|| OdsParseConstants.OdsDataFormat.NIC_DSP == dataFormat) {return vnfMaeCnDataProcessDomainService;} else if (OdsParseConstants.OdsDataFormat.VIM_OV2 == dataFormat|| OdsParseConstants.OdsDataFormat.PIM_OV2 == dataFormat) {return getVimPimDataProcessV2DomainService();} else if (OdsParseConstants.OdsDataFormat.VIM_OV3 == dataFormat|| OdsParseConstants.OdsDataFormat.PIM_OV3 == dataFormat) {return getVimPimDataProcessV3DomainService();} else if (OdsParseConstants.OdsDataFormat.VNFLOC_LCM == dataFormat) {return vnflocLcmDataProcessDomainService;} else {// 采集来的数据格式不支持log.warn("Incorrect data dataFormat. taskId:{}, tasksSn:{}, dataFormat:{} ", taskDto.getTaskId(),taskDto.getTaskSn(), dataFormat);}return null;}public VimPimDataProcessV2DomainService getVimPimDataProcessV2DomainService() {return new VimPimDataProcessV2DomainService(VIM_PIM_MODEL_V2_LIST, dataSource, vimPimRepository);}public VimPimDataProcessV3DomainService getVimPimDataProcessV3DomainService() {return new VimPimDataProcessV3DomainService(VIM_PIM_MODEL_V3_LIST, dataSource, vimPimRepository);}
}