背景
不同发展阶段的公司对代码质量的要求有所不同,往往在公司逐步走向壮大之后,技术中心越来越内卷,这就要求程序员不断完善代码,使其变得更灵活、更抽象!
实际情况是:程序员之间相互攀比,谁的代码越吊、越暗含一些所谓的思想;坐卧键盘间,笑谈天下事
代码优化
程序中有一段用于组合dto入参的代码,其用于请求第三方电子发票的入参,如下:
public void innerForInvoiceRsq(InvoiceTemplateDTO dto) {
ApplyInvoiceReqDto applyInvoiceReqDto = new ApplyInvoiceReqDto();
applyInvoiceReqDto.setStationId(dto.getStationId());
applyInvoiceReqDto.setPayOrderNo(dto.getWebOrderId());
applyInvoiceReqDto.setJshj(dto.getTicketPrice().add(dto.getInsuranceFee()).toPlainString());
StringBuilder sb = new StringBuilder();
sb.append("线路:" + dto.getLineName() + "\n");
sb.append("时间:" + dto.getFactStartTime() + "\n");
sb.append("票价:" + dto.getTicketPrice() + "\n");
sb.append("乘车人:");
switch (dto.getTicketType()) {
case "1":
sb.append("儿童");
break;
case "3":
sb.append("携童");
break;
default:
String name = StringUtils.isBlank(dto.getName()) ? "" : dto.getName();
String cardNo = StringUtils.isBlank(dto.getCardNo()) ? "" : dto.getCardNo();
sb.append(name + " ");
sb.append(cardNo);
break;
}
applyInvoiceReqDto.setBz(sb.toString());
ApplyInvoiceReqDto.Item item = new ApplyInvoiceReqDto.Item();
item.setDw("张");
item.setXmsl("1");
String projectName = parameterService.getParameterValueByCode(TicketConstant.H_InvoiceProjectName);
projectName = StringUtils.isBlank(projectName) ? "客运场站服务费" : projectName;
item.setXmmc(projectName);
item.setXmjshj(dto.getTicketPrice().add(dto.getInsuranceFee()).toString());
List<ApplyInvoiceReqDto.Item> items = new ArrayList<ApplyInvoiceReqDto.Item>();
items.add(item);
applyInvoiceReqDto.setItems(items);
}
以上代码,处于程序员鄙视链的底端,其本身功能是没有问题了(已经上了生产),其代码总行数为33行。但是,这份代码并没有更灵活、更抽象!会被其他程序员所鄙视!所以为了让代码看起来高大上,我们开始改造。
- 代码是构建发票请求的入参,考虑搞一个建造者,统一交给建造者去建造;
- 针对第三方的发票入参都差不多,考虑用一个模版模式,模版方法去生产;
- 代码内部有一个乘车人的信息拼装,考虑用一个策略模式,不同策略产生不同的乘车人信息;
实际情况是:上面都是我乱说的,主要目的就是让这段代码看上去高大上!
一、创建目录
二、模版方法
**
* InvoiceGenerator
* InvoiceGenerator作为抽象基类,定义了通用的发票生成逻辑。它接受一个泛型参数T,表示输入信息类型,要求该类型实现InvoiceInput接口。
* @author cll
* @Date v. 2024/4/25 17:33
* @Description 发票生产模版方法
**/
@Component
public abstract class InvoiceGenerator<T extends InvoiceInput> {
protected static final String DEFAULT_PROJECT_NAME = "客运场站服务费";
/**
* 策略模式,接口定义
* 抽象方法getPassengerInfoStrategy(),由子类实现以提供具体的乘车人信息生成策略
* */
protected abstract PassengerInfoStrategy getPassengerInfoStrategy();
/**
* 根据输入信息生成内部的发票请求对象。
*
* @param input 输入信息对象
* @return 应用发票请求DTO
*/
public ApplyInvoiceReqDto generateInvoice(T input) {
if (input == null) {
throw new IllegalArgumentException("输入信息不能为空");
}
ApplyInvoiceReqDto applyInvoiceReqDto = new ApplyInvoiceReqDto();
applyInvoiceReqDto.setType(input.getType());
applyInvoiceReqDto.setStationId(input.getStationId());
applyInvoiceReqDto.setPayOrderNo(input.getWebOrderId());
// 票价和保险费相加并处理精度
BigDecimal totalAmount = input.getTicketPrice().add(input.getInsuranceFee()).setScale(2, BigDecimal.ROUND_HALF_UP);
applyInvoiceReqDto.setJshj(totalAmount.toPlainString());
StringBuilder sb = new StringBuilder(100);
sb.append("线路:" + input.getLineName() + "\n");
sb.append("时间:" + input.getFactStartTime() + "\n");
sb.append("票价:" + input.getTicketPrice() + "\n");
sb.append("乘车人:");
String passengerInfo = getPassengerInfoStrategy().generatePassengerInfo(input);
sb.append(passengerInfo);
applyInvoiceReqDto.setBz(sb.toString());
// 使用建造者模式创建Item对象
ApplyInvoiceReqDto.Item item = new ApplyInvoiceReqDto.Item()
.setDw("张")
.setXmsl("1")
.setXmmc(getProjectName(input.getProjectName()))
.setXmjshj(totalAmount.toString());
List<ApplyInvoiceReqDto.Item> items = new ArrayList<>();
items.add(item);
applyInvoiceReqDto.setItems(items);
return applyInvoiceReqDto;
}
private String getProjectName(String projectName) {
return StringUtils.defaultIfBlank(projectName, DEFAULT_PROJECT_NAME);
}
}
三、InvoiceInput接口定义
public interface InvoiceInput {
String getStationId();
String getWebOrderId();
BigDecimal getTicketPrice();
BigDecimal getInsuranceFee();
String getLineName();
String getFactStartTime();
String getTicketType();
String getName();
String getCardNo();
String getType();
String getProjectName();
}
四、建造者
/**
* SellTicketInvoiceGenerator
*
* @author cll
* @Date v. 2024/4/25 17:38
* @Description 售票发票建造者,由该建造者去创建售票发票
**/
@Component
public class SellTicketInvoiceGenerator extends InvoiceGenerator<InvoiceTemplateDTO> {
/**
* 策略模式
* 支持处理不同类型的乘车人信息,返回的对象不同,对象内部实现的方法就可以不同
* */
@Override
protected PassengerInfoStrategy getPassengerInfoStrategy() {
return new SellInvoicePassengerInfoStrategy();
}
}
四、乘客策略接口
/**
* PassengerInfoStrategy
* 引入PassengerInfoStrategy接口,用于处理不同类型的乘车人信息展示
* @author cll
* @Date v. 2024/4/25 17:38
* @Description
**/
public interface PassengerInfoStrategy {
String generatePassengerInfo(InvoiceInput input);
}
五、售票乘客策略实现类
/**
* SellInvoicePassengerInfoStrategy
* 售票乘客信息策略service
* @author cll
* @Date v. 2024/4/25 18:07
* @Description
**/
@Component
public class SellInvoicePassengerInfoStrategy implements PassengerInfoStrategy {
@Override
public String generatePassengerInfo(InvoiceInput input) {
InvoiceTemplateDTO dto = (InvoiceTemplateDTO) input;
switch (dto.getTicketType()) {
case "1":
return "儿童";
case "3":
return "携童";
default:
String name = StringUtils.defaultIfBlank(dto.getName(), "");
String cardNo = StringUtils.defaultIfBlank(dto.getCardNo(), "");
return name + " " + cardNo;
}
}
}
业务代码调用方式
上述的代码总计60行,完成了模版方法、售票发票入参建造者类、售票乘客信息策略类。那么我们的业务代码,只需要用建造者去调用模版方法即可。如下:
@Resource
SellTicketInvoiceGenerator sellTicketInvoiceGenerator;
public ApplyInvoiceReqDto innerForInvoiceRsq(InvoiceTemplateDTO dto) {
invoiceTemplateDTO.setProjectName(parameterService.getParameterValueByCode(TicketConstant.H_InvoiceProjectName));
// 使用建造者调用模版方法,即可直接生成发票入参的请求类
return sellTicketInvoiceGenerator.generateInvoice(invoiceTemplateDTO);
}
代码描述
建造者 SellTicketInvoiceGenerator 是一个子类,继承父类 InvoiceGenerator,传入一个InvoiceTemplateDTO类,因此建造者具备了父类的模版方法generateInvoice(),建造者又去实现了父类的抽象方法getPassengerInfoStrategy(),在建造者的内部返回new SellInvoicePassengerInfoStrategy()对象,让这个对象去生成乘客信息。这里返回不同的对象就能获得不同的乘客信息(为策略模式)。
优点
- 业务service创建发票请求入参,只需要一行代码;屏蔽了创建过程;
- 售票建造者、退票手续费建造者,都可以通过模版方法来创建发票请求入参;
- 乘客信息采用策略模式,可以灵活修改和变化;
实际优点
可以在程序员同事之间装X;对初级的程序员,增加了代码的可读难度,拔高了自身地位;感觉自己是一个有思想有品味的程序员;飒爽、孤傲的背影、用了海飞丝、打了个冷颤;