Java中组合与继承的选择:AppleRequest DTO设计问题
解决方案
1. 用组合替代继承(首选)
既然AppleRequest和PriceRequest没有"is-a"关系,且要避免对方变更影响自身,组合是最适配的方案。核心思路是在AppleRequest中嵌入PriceRequest实例,仅暴露业务需要复用的字段,完全隔离不必要的依赖。
基于Lombok简化实现
利用@Delegate注解指定仅代理需要复用的字段方法,避免引入PriceRequest的冗余属性或后续新增的校验注解:
@Data public class AppleRequest { // 嵌入PriceRequest实例,仅暴露接口定义的方法 @Delegate(types = {PriceRequestPartial.class}) private PriceRequest priceRequest = new PriceRequest(); private String color; private String size; // 定义接口,明确需要复用的字段范围 public interface PriceRequestPartial { int getId(); void setId(int id); String getSupplierName(); void setSupplierName(String supplierName); } }
无Lombok的手动实现
如果项目未使用Lombok,可手动编写代理方法,直接调用内部PriceRequest的对应逻辑:
@Data public class AppleRequest { private PriceRequest priceRequest = new PriceRequest(); private String color; private String size; public int getId() { return priceRequest.getId(); } public void setId(int id) { priceRequest.setId(id); } public String getSupplierName() { return priceRequest.getSupplierName(); } public void setSupplierName(String supplierName) { priceRequest.setSupplierName(supplierName); } }
这种方式彻底切断了PriceRequest变更的传导路径,哪怕对方后续为字段添加@NotBlank或新增字段,只要你不主动扩展代理范围,就不会影响AppleRequest的正常工作。
2. 补充方案:用映射工具简化字段同步
如果需要在两个DTO之间频繁转换,可使用MapStruct这类映射工具,避免手动复制字段:
@Mapper public interface ApplePriceMapper { ApplePriceMapper INSTANCE = Mappers.getMapper(ApplePriceMapper.class); // 将AppleRequest的共享字段映射到PriceRequest PriceRequest appleToPrice(AppleRequest appleRequest); // 将PriceRequest的共享字段同步到AppleRequest void priceToApple(PriceRequest priceRequest, @MappingTarget AppleRequest appleRequest); }
后续字段变更时,仅需调整映射接口即可,无需修改业务代码。
针对问题的明确回答
- 应该用组合替代继承,这是符合面向对象设计原则的最优方案——既解决了字段复用需求,又规避了继承带来的耦合问题,同时纠正了"is-a"关系不匹配的设计缺陷。
- 避免
PriceRequest变更影响的核心是隔离依赖:- 通过组合仅暴露业务需要的字段,不直接继承整个类,切断对方新增注解、字段的传导路径;
- 不依赖
PriceRequest的完整结构,只提取自身需要的部分,从根源上消除不必要的耦合。
内容的提问来源于stack exchange,提问作者leaf_soba
相关产品推荐
相关产品推荐

