You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 16:52:11