Spring Controller代码重复问题:能否用工厂方法模式优化?
消除Spring控制器重复代码的最优方案
针对你遇到的WaterController和GasController代码高度重复的问题,工厂方法模式并不是最适合的选择,更推荐用泛型抽象控制器+模板方法的组合来复用逻辑——我们要解决的是控制器请求处理逻辑的重复,而非对象创建的封装。下面是具体实现步骤:
1. 统一实体的抽象接口
因为Water和Gas核心属性、行为一致,先定义通用接口让两个实体实现:
public interface Consumption { Long getId(); void setConsumedAmount(BigDecimal amount); void setMonth(Integer month); void setCustomer(Customer customer); } // Water实体实现接口 public class Water implements Consumption { // 原有字段、构造方法及get/set方法 } // Gas实体实现接口 public class Gas implements Consumption { // 原有字段、构造方法及get/set方法 }
2. 定义通用Repository接口
让两个Repository继承同一个泛型父接口,统一数据操作方法:
@NoRepositoryBean // 标记为抽象Repository,Spring不会实例化它 public interface ConsumptionRepository<T extends Consumption> extends JpaRepository<T, Long> { List<T> findAllByCustomerId(Long customerId); } // WaterRepository继承通用接口 public interface WaterRepository extends ConsumptionRepository<Water> {} // GasRepository继承通用接口 public interface GasRepository extends ConsumptionRepository<Gas> {}
3. 抽象控制器封装通用逻辑
把两个控制器里重复的GET/POST方法抽成抽象类,用泛型指定实体和Repository类型,只保留需要子类实现的差异化逻辑:
public abstract class AbstractConsumptionController<T extends Consumption, R extends ConsumptionRepository<T>> { protected final R repository; protected final CustomerRepository customerRepository; // 构造方法注入依赖 public AbstractConsumptionController(R repository, CustomerRepository customerRepository) { this.repository = repository; this.customerRepository = customerRepository; } @GetMapping("/{id}") public ResponseEntity<?> getById(@PathVariable Long id) { Optional<T> record = repository.findById(id); if (record.isEmpty()) { return new ResponseEntity<>(getRecordType() + " record not found by id " + id, HttpStatus.BAD_REQUEST); } return new ResponseEntity<>(record.get(), HttpStatus.OK); } @PostMapping public ResponseEntity<String> createConsumption(@Valid @RequestBody DataDto dataDto) { Optional<Customer> customer = customerRepository.findById(dataDto.getCustomerId()); if (customer.isEmpty()) { return new ResponseEntity<>("customerId " + dataDto.getCustomerId() + " not found", HttpStatus.BAD_REQUEST); } // 由子类创建具体实体实例 T consumption = createInstance(dataDto.getConsumedAmount(), dataDto.getMonth(), customer.get()); T saved = repository.save(consumption); return new ResponseEntity<>(saved.getId().toString(), HttpStatus.OK); } @GetMapping("/{id}/data") public List<T> getByCustomerId(@PathVariable Long id) { return repository.findAllByCustomerId(id); } // 子类需实现:返回实体类型名称(用于错误提示) protected abstract String getRecordType(); // 子类需实现:创建具体的实体对象 protected abstract T createInstance(BigDecimal consumedAmount, Integer month, Customer customer); }
4. 简化具体控制器实现
现在WaterController和GasController只需继承抽象类,实现两个抽象方法即可,代码量大幅减少:
@RestController @RequestMapping("/water") public class WaterController extends AbstractConsumptionController<Water, WaterRepository> { public WaterController(WaterRepository repository, CustomerRepository customerRepository) { super(repository, customerRepository); } @Override protected String getRecordType() { return "water"; } @Override protected Water createInstance(BigDecimal consumedAmount, Integer month, Customer customer) { return new Water(consumedAmount, month, customer); } }
@RestController @RequestMapping("/gas") public class GasController extends AbstractConsumptionController<Gas, GasRepository> { public GasController(GasRepository repository, CustomerRepository customerRepository) { super(repository, customerRepository); } @Override protected String getRecordType() { return "gas"; } @Override protected Gas createInstance(BigDecimal consumedAmount, Integer month, Customer customer) { return new Gas(consumedAmount, month, customer); } }
关于工厂方法模式的补充
如果一定要用工厂方法,可把createInstance的逻辑抽成独立工厂类,但当前场景完全没必要——抽象控制器里的createInstance已经承担了“工厂方法”的职责,属于模板方法模式的一部分,既解决了逻辑复用,又保留了子类的灵活性。
内容的提问来源于stack exchange,提问作者Volodymyr
相关产品推荐
相关产品推荐

