java开发之消除冗余代码的3种方法

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

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

假设我们要做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{

	@Override
	protected void addOtherMaterial(Cake cake) {
		cake.setCakeName("抹茶蛋糕");
		cake.setSupplement("抹茶粉");
		System.out.println("当前正在制作好吃的抹茶蛋糕");
	}

}

可可蛋糕CocoaCakeService的实现: 

@Service(value = "CocoaCakeService")
public class CocoaCakeService extends AbstractCakeService{

	@Override
	protected void addOtherMaterial(Cake cake) {
		cake.setCakeName("可可蛋糕");
		cake.setSupplement("可可粉");
		System.out.println("当前正在制作好吃的可可蛋糕");
	}

}

草莓蛋糕CocoaCakeService的实现: 

@Service(value = "StrawberryCakeService")
public class StrawberryCakeService extends AbstractCakeService{

	@Override
	protected 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 {
	
	@Autowired
	private 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 {
	
	@Autowired
	private 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/#/

猜你喜欢

转载自blog.csdn.net/j1231230/article/details/106042105
今日推荐