一.利用工厂模式+模板方法模式

我们以做蛋糕为例来演示如何消除重复代码。

假设我们要做3种不同口味的蛋糕,分别是抹茶,可可和草莓蛋糕,实际上3种蛋糕的制作方法是极其相似的,只有添加的粉剂不

同,如果用代码来实现蛋糕制作流程,要写大量重复代码,容易产生BUG,我们可以使用工厂模式和模板方法模式来避免重复。

首先定义一个蛋糕类Cake:

@Data
public class Cake {// 蛋糕名称String cakeName;Integer sugar;Integer eggs;Integer flour;// 添加剂(可可粉,抹茶粉,草莓)String supplement;
}

再定义一个制作蛋糕的抽象父类:

@Service
public abstract class AbstractCakeService {//处理做蛋糕的重复逻辑在父类实现public Cake doCake(){Cake cake = new Cake();cake.setEggs(2);cake.setFlour(250);cake.setSugar(30);//让子类实现不同的蛋糕处理addOtherMaterial(cake);return cake;}// 不同属性的赋值留给子类实现protected abstract void addOtherMaterial(Cake cake);}

我们定义3个不同的子类:抹茶蛋糕,可可蛋糕,草莓蛋糕制作,他们都继承抽象的父类AbstractCakeService,分别为

TeaCakeService,CocoaCakeService,StrawberryCakeService。

抹茶蛋糕TeaCakeService的实现:

@Service(value = "TeaCakeService")
public class TeaCakeService extends AbstractCakeService{@Overrideprotected void addOtherMaterial(Cake cake) {cake.setCakeName("抹茶蛋糕");cake.setSupplement("抹茶粉");System.out.println("当前正在制作好吃的抹茶蛋糕");}}

可可蛋糕CocoaCakeService的实现:

@Service(value = "CocoaCakeService")
public class CocoaCakeService extends AbstractCakeService{@Overrideprotected void addOtherMaterial(Cake cake) {cake.setCakeName("可可蛋糕");cake.setSupplement("可可粉");System.out.println("当前正在制作好吃的可可蛋糕");}}

草莓蛋糕CocoaCakeService的实现:

@Service(value = "StrawberryCakeService")
public class StrawberryCakeService extends AbstractCakeService{@Overrideprotected void addOtherMaterial(Cake cake) {cake.setCakeName("草莓蛋糕");cake.setSupplement("新鲜草莓");System.out.println("当前正在制作好吃的草莓蛋糕");}}

3种蛋糕制作都叫 XXXCakeService,那我们就可以把蛋糕类型字符串拼接 CakeService构成 Bean 的名称,然后利用 Spring 的

IoC 容器,通过 Bean 的名称直接获取到 AbstractCakeService,调用其 doCake方法来实现不同蛋糕的制作调用,这就是借助

Spring 容器实现了工厂模式。

调用的控制层代码如下:

@RestController
public class cakeController {@Autowiredprivate ApplicationContext applicationContext;@GetMapping("doCake")public Cake doCake(@RequestParam("supplement") String supplement) {AbstractCakeService cake = (AbstractCakeService) applicationContext.getBean(supplement + "CakeService");return cake.doCake();}
}

传入需要的添加剂,实现蛋糕的制作:

这样一来,我们利用工厂模式 + 模板方法模式,不仅消除了重复代码,还避免了修改既有代码的风险。这就是设计模式中的开闭

原则:对修改关闭,对扩展开放。

git代码实现路径:

https://github.com/jichunyang19931023/dailyDemo/tree/master/cake

二.利用注解 + 反射消除重复代码

假如我们要和外部系统进行接口服务对接,需要一个接口来输出接口的定义和传参等信息,此时我们可以利用注解+反射的机制来解决这个问题。

如果有一个创建票据的接口需要生成API的信息,要实现接口逻辑和逻辑实现的剥离,首先需要以 POJO 类(只有属性没有

任何业务逻辑的数据类)的方式定义所有的接口参数,如下所示:

@Data
public class CreateTicketAPI {// 用户名String userName;// 密码String password;// 当前时间的时间戳Long currentTime;
}

我们可以通过自定义注解为接口和所有参数增加一些元数据。如下所示,我们定义一个接口的注解 API,包含接口接口说明, URL 地址和请求方式:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface API {String desc() default "";String url() default "";String type() default "GET";
}

然后,我们再定义一个自定义注解 @APIField,用于描述接口的每一个字段规范,包含参数的可否为空、类型和说明三个属性:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface APIField {// 可否为空boolean isRequired() default false;// 参数类型String type() default "";// 参数说明,备注String remark() default "";
}

修改原始的POJO 类,补充接口和每个参数字段的元数据(也就是对应的属性相关信息),继承的 AbstractAPI 类是一个空实

现,这个案例中的接口并没有公共数据可以抽象放到基类:

@API(url = "/creatTicket", desc = "创建票据接口", type = "GET")
@Data
public class CreateTicketAPI extends AbstractAPI{@APIField(isRequired = true, type = "String", remark = "用户名称")String userName;@APIField(isRequired = true, type = "String", remark = "密码")String password;@APIField(isRequired = false, type = "Long", remark = "当前时间的时间戳")Long currentTime;
}

以上,我们通过注解实现了对 API 参数的描述。接下来,我们再看看反射如何配合注解实现接口属性和参数说明:

@Slf4j
@Service
public class APIService {public JSONObject apiCreate(AbstractAPI api){JSONObject result = new JSONObject();// 从API注解获取请求说明API apiObj = api.getClass().getAnnotation(API.class);result.put("apiDesc", apiObj.desc());result.put("apiUrl", apiObj.url());result.put("apiType", apiObj.type());JSONArray apiParams = JSONUtil.createArray();Arrays.stream(api.getClass().getDeclaredFields()) // 获得所有字段.filter(field -> field.isAnnotationPresent(APIField.class)) // 查找标记了注解的字段.peek(field -> field.setAccessible(true)) // 设置可以访问私有字段.forEach(field -> {JSONObject paramObj = new JSONObject();// 获得注解APIField apiField = field.getAnnotation(APIField.class);Object value = "";try {// 反射获取字段值value = field.get(api);} catch (IllegalAccessException e) {log.error("反射获取字段值发生异常", e);}paramObj.put("paramName", field.getName());paramObj.put("paramType", apiField.type());paramObj.put("isRequired", apiField.isRequired());paramObj.put("remark", apiField.remark());paramObj.put("paramValue", value);apiParams.add(paramObj);});result.put("apiParams", apiParams);return result;}
}

最后控制层进行调用:

@RestController
public class APIController {@Autowiredprivate APIService apiService;@GetMapping(value = "/getApi")public JSONObject getApi(String userName, String password, Long currentTime) {CreateTicketAPI ticketAPI = new CreateTicketAPI();ticketAPI.setUserName(userName);ticketAPI.setPassword(password);ticketAPI.setCurrentTime(currentTime);return apiService.apiCreate(ticketAPI);}
}

许多涉及类结构性的通用处理,都可以按照这个模式来减少重复代码。反射给予了我们在不知晓类结构的时候,按照固定的逻辑

处理类的成员;而注解给了我们为这些成员补充元数据的能力,使得我们利用反射实现通用逻辑的时候,可以从外部获得更多我

们关心的数据。

git代码实现路径:

https://github.com/jichunyang19931023/dailyDemo/tree/master/api

三.利用属性拷贝工具消除重复代码

对于三层架构的系统,考虑到层之间的解耦隔离以及每一层对数据的不同需求,通常每一层都会有自己的 POJO 作为数据实体。

比如,数据访问层的实体一般叫作 DataObject 或 DO,业务逻辑层的实体一般叫作 Domain,表现层的实体一般叫作 Data

Transfer Object 或 DTO。如果手动写这些实体之间的赋值代码,同样容易出错,所以我们可以利用属性拷贝工具来消除重复代

码。

使用类似 BeanUtils 这种 Mapping 工具来做 Bean 的转换,copyProperties 方法还允许我们提供需要忽略的属性:


ComplicatedOrderDTO orderDTO = new ComplicatedOrderDTO();
ComplicatedOrderDO orderDO = new ComplicatedOrderDO();
BeanUtils.copyProperties(orderDTO, orderDO, "id");
return orderDO;

或者可以使用一个很棒的工具包hutool来实现,下面是官方文档网址,支持不同类型的拷贝,十分方便:

https://www.hutool.cn/docs/#/

java开发之消除冗余代码的3种方法相关推荐

  1. java single instance_java单例模式(具体代码显现)两种方法

    判断是否存在 /** * 懒汉式 */ public class LazySingleInstance { // 私有构造方法 private LazySingleInstance(){}; // 私 ...

  2. java冗余_Java使用lombok消除冗余代码的方法步骤

    一.项目背景 在写Java程序的时候经常会遇到如下情形: 新建了一个Class类,然后在其中设置了几个字段,最后还需要花费很多时间来建立getter和setter方法. lombok项目的产生就是为了 ...

  3. 简化开发|Lombok神器带你消除冗余代码

    前言 Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务过程中冗余的代码,尤其是简单的Java模型对象(POJO).而当我们如果在开发环境中使用Lombok开发插 ...

  4. Java开发中经常使用到的几种WebService技术实现方案

    Java开发中经常使用到的几种WebService技术实现方案 随着异构系统互联需求的不断增加,WebService的重要性也日益彰显出来.凭借webservice,我们可以实现基于不同程序语言的项目 ...

  5. Eclipse远程调试Java代码的三种方法

    Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...

  6. java property xml,Java开发中读取XML与properties配置文件的方法

    相关阅读: 1. XML文件: 什么是XML?XML一般是指可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言. 2.XML文件的优点: 1)XML文档内容和结构完 ...

  7. eclipse 远程调试java_Eclipse远程调试Java代码的三种方法

    Eclipse远程调试Java代码的三种方法, 第1种方法是用来调试已经启动的Java程序,Eclipse可以随时连接到远程Java程序进行调试, 第2种方法可以调试Java程序启动过程,但是Ecli ...

  8. Java构造和解析Json数据的两种方法详解一

    在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面首先介绍用json-lib构造和解析Jso ...

  9. Java 判断字符串是否为空的四种方法、优缺点与注意事项

    以下是Java 判断字符串是否为空的四种方法: 方法一: 最多人使用的一个方法, 直观, 方便, 但效率很低: if(s == null ||"".equals(s)); 方法二: ...

最新文章

  1. 在VS上配置OpenCV
  2. python aipspeech_Python调用百度API实现语音识别(二)
  3. git error(win下)
  4. msql查询指定日期
  5. EXCEL文件上传与下载
  6. [深度学习基础] 深度学习基础及数学原理
  7. 长江大学微型计算机课设报告,长江大学B第一学期计算机基础试卷.doc
  8. java学习笔记十二
  9. eclipse常用的快捷键
  10. [PYTHON] 深度解析copy.copy() 与 copy.deepcopy()
  11. javascript的运算(小结)
  12. python下载bt文件_python获取bt种子的详细信息
  13. Json-lib, 实现Java对象与JSON数据格式的互转
  14. 【IPM2020】一种处理多标签文本分类的新颖推理机制
  15. Linux 用户管理
  16. WS小世界网络python快速实现——调用networkx包
  17. 二、Esp32开发环境快速搭建(vscode+PlatformIO IED)
  18. world分节及分节首页分节页码总页码设置方法
  19. css3 animation 实现帧动画
  20. flutter系列之:如丝般顺滑的SliverAppBar

热门文章

  1. 数学常数e的含义 (转载)
  2. 易语言toJava 集合类
  3. Linux下对grub引导文件丢失进行恢复
  4. 为什么找不到用户和计算机名,win10电脑“本地用户和组”找不到该怎么办啊?...
  5. 用matlab画饼状图和相量图
  6. Arcgis操作系列15 - 西安80/wgs84转2000
  7. 非常全面的CSDN-markdown编辑器!!!!!!
  8. 想转行当程序员的必看!揭秘ARouter路由机制,Android校招面试指南
  9. 逆战--关于微信小程序
  10. ELK - X-Pack设置用户密码