当前位置: 首页 > web >正文

浅谈装饰模式

一、前言

        hello大家好,本次打算简单聊一下装饰者模式,其实写有关设计模式的内容还是蛮有挑战性的,首先呢就是小永哥实力有限担心说不明白,其次设计模式是为了解决某些问题场景,在当前技术生态圈如此完善的情况下,能遇到使用设计模式的时候其实还是蛮少见的,而且我一直认为解决方案本身没有好坏之分,只有经过取舍适合当前的才是最好的。任何事物都是双刃剑,包括设计模式,在没有适合场景的情况下强行使用反而会造成过度设计,这样就不好了。

        不过呢前几天小永哥确实遇到了一个小场景能用一下装饰者模式,不过作为演示呢我就不完整复刻那些复杂的代码了,咱来点简易的类比一下,尽量以少的代码能说明白。

二、业务场景

        当时的业务场景是一个Excel导入功能,我们都知道导入时,需要读取逐行读取Excel数据,每行数据又要对应列的数据读取出来赋值给实体类对象,每一行就是一个实体类对象,最后将这些实体类对象设置到提前声明好的list集合中并调用批量保存完成导入,简易导入请看下述代码。

package com.relation;import com.alibaba.fastjson2.JSON;
import org.apache.poi.ss.usermodel.*;
import org.junit.jupiter.api.Test;import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 20:20*/
public class ImportTest {@Testpublic void test() throws IOException {ArrayList<Map<String,Object>> mapList = new ArrayList<>();// 导入的源文件String path = "E:\\test\\demo.xlsx";// 工作薄对象Workbook workbook = WorkbookFactory.create(new FileInputStream(path));// 工作表 sheetSheet sheet = workbook.getSheetAt(0);// 行int rows = sheet.getPhysicalNumberOfRows();DataFormatter dataFormatter = new DataFormatter();// 行的头信息,第一行,可以不处理for (int i = 1; i < rows; i++) {Row row = sheet.getRow(i);Map<String,Object> demoData = new HashMap<>();// 第一个单元格,因为是序号,可以不要// 从第二个单元格开始获取名称String name = dataFormatter.formatCellValue(row.getCell(1));demoData.put("name",name);// 第三个单元格获取编号String code = dataFormatter.formatCellValue(row.getCell(2));demoData.put("code",code);// 把组装好的 student对象,存入集合mapList.add(demoData);}System.out.println(JSON.toJSONString(mapList));}
}

         以上就是一个简易的excel上传代码,运行测试类也能从excel中获取到数据,一个很正常和普通的小需求,想来以各位老铁的本事,这些都不叫事。下面有请我们T哥给我上点强度。

三、来自T哥的折磨

        T哥:一般找我来都没啥好事,不过能给你填点堵我还是很乐意的。

        小白:T哥你客气了,有事您吩咐,没有困难我制造困难也给你整明白儿的。

        T哥:好了,废话少说,为了减少用户手输的错误率,你看能不能给表格内容去空格呀,防止用户不小心在前后多输空格然后又检查不出来。

        小白:那太简单了,你就瞧好吧......

        3.1、去空格需求的实现。

        很简单嘛,给设置值的位置调用一下String自带的trim()方法不就好了,很轻松呀。

        3.2、来自T哥的新需求。

        T哥:这么快就实现了,不愧是专业的java开发人员,那个什么,客户要求除去空格之外,需要将每个数据都加上一个前缀AAA。这个能实现吧。

        小白:好吧,也不是什么问题。

我们可以看到小白很快又搞定了,但是小白陷入了沉思。

        小白:不对呀,为什么我每次都要改所有单元格设置的位置呢?我得想办法写个方法把这些集中起来,这样我只需要在第一次修改的时候改动位置稍微多一点,以后每次我修改集中起来的代码就好了。说干就干。

        

package com.relation;import com.alibaba.fastjson2.JSON;
import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.junit.jupiter.api.Test;import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 20:20*/
public class ImportTest {@Testpublic void test() throws IOException {// 将Excel文件导入到数据库中ArrayList<Map<String,Object>> mapList = new ArrayList<>();// 导入的源文件String path = "E:\\test\\demo.xlsx";// 工作薄对象Workbook workbook = WorkbookFactory.create(new FileInputStream(path));// 工作表 sheetSheet sheet = workbook.getSheetAt(0);// 行int rows = sheet.getPhysicalNumberOfRows();DataFormatter dataFormatter = new DataFormatter();// 行的头信息,第一行,可以不处理for (int i = 1; i < rows; i++) {Row row = sheet.getRow(i);Map<String,Object> demoData = new HashMap<>();// 第一个单元格,因为是序号,可以不要// 从第二个单元格开始获取名称String name = dataFormatter.formatCellValue(row.getCell(1));demoData.put("name",getStr(name));// 第三个单元格获取编号String code = dataFormatter.formatCellValue(row.getCell(2));demoData.put("code",getStr(code));// 把组装好的 student对象,存入集合mapList.add(demoData);}System.out.println(JSON.toJSONString(mapList));}private String getStr(String data){if(StringUtils.isEmpty(data)){return data;}return "AAA"+data.trim();}
}

        小白抽取了一个方法,专门用来处理表格内容,好像比之前要好很多了。

        T哥:你进步了,已经开始能自查并完善自己的代码了......

        小白:当然了,每次都改那么多地方,我累,能少改干嘛要多改呢?

        T哥:那既然你这么说,那你抽取getStr方法之后,不还是得在第一次修改的时候,逐个找到每个获取表格的位置去替换为使用getStr方法吗?

        小白:还好吧,只有两处而已,有必要再折腾吗?

        T哥:没错,现在确实是只有名称和编码两个属性,那万一有几十个属性呢?你也要一个个去修改吗?不是你自己说能少写一行代码就不多写一行吗?

        小白:那好吧,我再想想办法,毕竟我是一个面向对象工程师......

        3.3、面向对象方式改造
package com.relation;import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 21:47*/
public class DataFormatterExpand extends DataFormatter {@Overridepublic String formatCellValue(Cell cell) {String cellValue = super.formatCellValue(cell);if(StringUtils.isEmpty(cellValue)){return cellValue;}return "AAA"+cellValue.trim();}
}
package com.relation;import com.alibaba.fastjson2.JSON;
import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.junit.jupiter.api.Test;import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 20:20*/
public class ImportTest {@Testpublic void test() throws IOException {// 将Excel文件导入到数据库中ArrayList<Map<String,Object>> mapList = new ArrayList<>();// 导入的源文件String path = "E:\\test\\demo.xlsx";// 工作薄对象Workbook workbook = WorkbookFactory.create(new FileInputStream(path));// 工作表 sheetSheet sheet = workbook.getSheetAt(0);// 行int rows = sheet.getPhysicalNumberOfRows();DataFormatterExpand dataFormatter = new DataFormatterExpand();// 行的头信息,第一行,可以不处理for (int i = 1; i < rows; i++) {Row row = sheet.getRow(i);Map<String,Object> demoData = new HashMap<>();// 第一个单元格,因为是序号,可以不要// 从第二个单元格开始获取名称String name = dataFormatter.formatCellValue(row.getCell(1));demoData.put("name",name);// 第三个单元格获取编号String code = dataFormatter.formatCellValue(row.getCell(2));demoData.put("code",name);// 把组装好的 student对象,存入集合mapList.add(demoData);}System.out.println(JSON.toJSONString(mapList));}
}

 

        我们可以看到,小白扩展了一个类并继承了DataFormatter并重写了formatCellValue方法,先调用父类的formatCellValue,然后再对获取的值进行统一扩展完成任务。

        T哥:很好,一个非常标准的OO设计。

        小白:当然了,我可是一个专业的OO程序员。

        T哥:其实做到这一步已经设计的很不错了,代码改动也少,变化的代码也抽取出去了,那还有可以改进的地方吗?

        小白:我不是很明白,现在不是很完美了吗?为什么还要改进?

        T哥:如果针对此功能需求到此为止,那你的设计可以说完美,但是如果后续还有变化呢?你还能优雅保持你的扩展性吗?

        小白:我想是可以的。

        T哥:好的,那咱们继续,

        3.4、T哥的BUFF叠加

        T哥:来新需求了,我们需要再加上BBB后缀。

        小白:这简单,我继续修改一下。

        T哥:新需求来了,要求把AAA前缀去掉......

        小白:T哥,你怕不是玩死我吧?

        T哥:你说的什么话,不是你让我来给你提需求吗?

        小白:我*******

        T哥:我*******

        然后T哥和小白打起来了.........

        小永哥:小白你先放手,这事儿哥帮你办了,你先消消气。

        3.5、小永哥的改造
package com.relation.expand;import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 21:47*/
public class DataFormatterTrimExpand extends DataFormatter {private DataFormatter dataFormatter;public void setDataFormatter(DataFormatter dataFormatter) {this.dataFormatter = dataFormatter;}@Overridepublic String formatCellValue(Cell cell) {String cellValue = dataFormatter.formatCellValue(cell);if(StringUtils.isEmpty(cellValue)){return cellValue;}return cellValue.trim();}
}

        

package com.relation.expand;import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 21:47*/
public class DataFormatterPrefixExpand extends DataFormatter {private DataFormatter dataFormatter;public void setDataFormatter(DataFormatter dataFormatter) {this.dataFormatter = dataFormatter;}@Overridepublic String formatCellValue(Cell cell) {String cellValue = dataFormatter.formatCellValue(cell);if(StringUtils.isEmpty(cellValue)){return cellValue;}return "AAA"+cellValue;}
}

package com.relation.expand;import com.relation.common.utils.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 21:47*/
public class DataFormatterSuffixExpand extends DataFormatter {private DataFormatter dataFormatter;public void setDataFormatter(DataFormatter dataFormatter) {this.dataFormatter = dataFormatter;}@Overridepublic String formatCellValue(Cell cell) {String cellValue = dataFormatter.formatCellValue(cell);if(StringUtils.isEmpty(cellValue)){return cellValue;}return cellValue+"BBB";}
}
package com.relation.expand;import org.apache.poi.ss.usermodel.DataFormatter;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 22:27*/
public class DataFormatterFactory {public static DataFormatter createDataFormatter(){DataFormatter dataFormatter = new DataFormatter();//创建去空格扩展类DataFormatterTrimExpand dataFormatterTrimExpand = new DataFormatterTrimExpand();//创建加前缀扩展类DataFormatterPrefixExpand dataFormatterPrefixExpand = new DataFormatterPrefixExpand();//创建加后缀扩展类DataFormatterSuffixExpand dataFormatterSuffixExpand = new DataFormatterSuffixExpand();//按需求进行组合//将原生DataFormatter设置到去空格扩展类来达到去空格功能dataFormatterTrimExpand.setDataFormatter(dataFormatter);//将去空格扩展类设置到加前缀扩展类,来给去空格后的值加前缀dataFormatterPrefixExpand.setDataFormatter(dataFormatterTrimExpand);//将加前缀扩展类设置到加后缀扩展类,给加完前缀值在加上后缀dataFormatterSuffixExpand.setDataFormatter(dataFormatterPrefixExpand);return dataFormatterSuffixExpand;}
}
package com.relation;import com.alibaba.fastjson2.JSON;
import com.relation.expand.DataFormatterFactory;
import com.relation.expand.DataFormatterTrimExpand;
import org.apache.poi.ss.usermodel.*;
import org.junit.jupiter.api.Test;import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;/*** @author huhy* @version 1.0* @Description:* @ClassName Date:2025/5/10 20:20*/
public class ImportTest {@Testpublic void test() throws IOException {// 将Excel文件导入到数据库中ArrayList<Map<String,Object>> mapList = new ArrayList<>();// 导入的源文件String path = "E:\\test\\demo.xlsx";// 工作薄对象Workbook workbook = WorkbookFactory.create(new FileInputStream(path));// 工作表 sheetSheet sheet = workbook.getSheetAt(0);// 行int rows = sheet.getPhysicalNumberOfRows();DataFormatter dataFormatter = DataFormatterFactory.createDataFormatter();// 行的头信息,第一行,可以不处理for (int i = 1; i < rows; i++) {Row row = sheet.getRow(i);Map<String,Object> demoData = new HashMap<>();// 第一个单元格,因为是序号,可以不要// 从第二个单元格开始获取名称String name = dataFormatter.formatCellValue(row.getCell(1));demoData.put("name",name);// 第三个单元格获取编号String code = dataFormatter.formatCellValue(row.getCell(2));demoData.put("code",code);// 把组装好的 student对象,存入集合mapList.add(demoData);}System.out.println(JSON.toJSONString(mapList));}
}

我们可以看到,经过小永哥的改造,前缀和后缀都加上了,但是好像代码量多了很多。

        小白:永哥,你这是什么名堂,为啥你改造完以后代码量反而更多了呢?

        小永哥:你说的对,初期确实代码量比你的OO设计要多了不少,待我给你好好介绍一下。

        3.6、代码解析

        1)、如下图所示,我们为每一种扩展都创建了对应的实现类。

        2)、我们改造了扩展类,我们加了一个DataFormatter类型的成员变量,我们不再调用父类的方法,而是调用成员变量的方法。

        3)、这样做的好处是,成员变量可以是原生的类,也可是我们的扩展类,这样的话,我们就可以按照需求灵活对扩展类进行组合。

        4)、我们创建了一个简易的工厂DataFormatterFactory来生成DataFormatter,将我们组合的逻辑封装在工厂类的createDataFormatter方法中,从此刻开始,我们已经将所有变化的东西,从我们的主业务逻辑中抽取了出来。

        5)、经过一系列的改造,我们的代码可扩展性和可维护性已经大大的提高了,需求变化时,如果我们已经有对应的扩展类,则直接进行组合即可,如果没有对应的扩展类,我们可以创建新的扩展类,并进行新的组合。

        T哥:小永哥有两下子哈,小白你能总结一下你小永哥这么做符合哪些原则吗?

        小白:首先,小永哥对每一种扩展创建对应的实现类,满足了单一职责。对于新的扩展,创建新的实现类,而不对业务代码进行修改,符合开闭原则,但好像对工厂类有改动,好像又没有很完美的符合开闭原则。

        T哥:你说的很对,开闭原则是对修改关闭,对新增开放以大大减少对已有稳定代码的改动,以减少需求变化对系统的影响,但是要完全做到开闭原则很难,我们在开发过程中或多或少都会涉及到对原有代码的改动,这个是不可避免的,小永哥可以说将代码改动控制在很小范围之内了,如果出现问题,排查范围也会小很多。

        小永哥:不好意思,我插一句哈,在扩展类的改造中,我使用了组合模式,利用组合模式松耦合的特性来提升系统的弹性,小白这一点你要牢记,有时候有一个比是一个要更好,说的更直白一点,就是组合模式要比继承的灵活性更强。

        小白:永哥,我记住了。

        3.7、总结

        经过了一系列的改造,我们已经将代码改造好了,特别是最后一次改造,运用的就是经典的装饰模式。通过自由组合的方式对原始的类进行层层装饰,从而达到我们的目的。

四、结语

        又耗费了不少脑细胞,写了这么多希望可以通过这个简单的案例将装饰模式能聊清楚,装饰模式虽然很经典,但是在小永哥的心中,威力最大的还是策略模式,特别是策略模式+模版方法模式,威力巨大,能灵活多变的应对繁琐的业务变化,小永哥好好构思一下业务场景,争取下把和大家聊聊这两种设计模式以及这两种设计模式的组合体,本次就到这了,谢谢大家,晚安。

http://www.xdnf.cn/news/5164.html

相关文章:

  • 旅游推荐数据分析可视化系统算法
  • 数据结构中的栈与队列:原理、实现与应用
  • C++学习-入门到精通-【6】指针
  • 【AI智能推荐系统】第七篇:跨领域推荐系统的技术突破与应用场景
  • [RoarCTF 2019]Easy Calc1
  • 【许可证】Open Source Licenses
  • 异地多活单元化架构下的微服务体系
  • 某某文KU下载工具,请低调再低调使用!
  • Hadoop 2.x设计理念解析
  • 【大模型】使用 LLaMA-Factory 进行大模型微调:从入门到精通
  • AI 驱动数据库交互技术路线详解:角色、提示词工程与输入输出分析
  • Linux——Mysql索引和事务
  • 【验证码】⭐️集成图形验证码实现安全校验
  • Linux进程管理
  • journalctl使用
  • 网络地址转换之SNAT和DNAT
  • 《自动驾驶封闭测试场地建设技术要求》 GB/T 43119-2023——解读
  • Web3 学习全流程攻略
  • 用AI写简历是否可行?
  • MacOS 用brew 安装、配置、启动Redis
  • 低成本自动化改造技术锚点深度解析
  • L48.【LeetCode题解】904. 水果成篮
  • 《 指针变量的创建:初探内存世界的钥匙》
  • 【技巧】如何把win10 wsl的安装目录从c盘迁移到d盘
  • 【Gradio】helloworld程序
  • 嵌入式开发学习(第二阶段 C语言基础)
  • 随笔-近况
  • 插槽、生命周期
  • QWindowkit 实现无边框,阴影支持系统边栏缩放等功能
  • 深入理解C/C++内存管理:从基础到高级优化实践