构造函数中调用传入对象的Getter初始化类属性是否符合最佳实践?
类A两种设计方案的最佳实践分析
问题背景
我有一个类A,其属性是类B和类C的属性子集:C的少量属性在构造函数中直接赋值,而B的属性较多,因此我在A的构造函数中传入B对象,调用其Getter方法为A的x、y、list属性赋值,代码如下:
public class A { private int x; private int y; private List<> list; public A(B b) { this.x = b.getX(); this.y = b.getY(); this.list = b.getList(); } } public class B { private int x; private int y; private int z; private String string; private Set<> set; private List<> list; //constructor, setters and getters }
同事建议改为让A直接持有B对象,代码如下:
public class A { private B b; public A(B b) { this.b=b; } }
但这种方案需要将现有代码中的a.getX()重构为a.getB().getX()、a.getList()重构为a.getB().getList(),改动量很大。
我想了解这两种方案哪种符合最佳实践及原因:我认为自己的做法可避免使用A的对象与B的类结构耦合;同事则担忧构造函数传入B仅调用其Getter的做法存在问题。
补充场景:B和C是对应两张关联表的Entity类,A用于其他业务逻辑,且A是在HQL查询中构造的,使用工厂模式将B转为A不可行。
方案分析与结论
核心差异
两种方案的本质区别是:数据复制(你的方案) vs 依赖持有(同事的方案)
你的方案:数据复制,解耦结构
优势
- 完全解耦A与B:A只暴露自身业务需要的属性,外部调用者无需知晓B的存在。后续B的属性名称、Getter方法变更时,只需调整A的构造函数,不会影响大量调用A的业务代码。
- 符合单一职责:A作为特定业务的载体,仅保留必要属性,避免外部通过A访问B中无关的属性(如z、string、set),职责清晰。
- 适配HQL场景:HQL中可直接通过构造函数传入B对象或其属性实例化A,无需额外转换逻辑,契合当前技术限制。
潜在问题与优化
- 手动赋值代码冗余:若后续B需要传递给A的属性增多,构造函数会变长。可通过Bean映射工具(如
Spring BeanUtils.copyProperties(b, this))简化,减少手动代码的出错概率。 - 数据同步问题:如果B的属性后续更新,A中的复制值不会自动同步,但结合场景(A是HQL查询结果),这反而能保证数据快照的一致性,是合理的。
同事的方案:持有B对象,复用属性
优势
- 代码简洁:无需重复定义属性和赋值逻辑,直接复用B的Getter方法。
- 自动同步数据:若B的属性值更新,A通过
getB()获取的属性会同步变化,但这在查询快照场景下是缺点,会导致数据不一致。
严重问题
- 强耦合违反迪米特法则:A的调用者必须依赖B的类结构,后续B的任何变更都会直接影响所有调用
a.getB().xxx()的代码,维护成本极高。 - 职责模糊:A沦为B的包装器,失去了作为特定业务载体的意义,外部可通过A访问B的所有属性,增加业务逻辑混乱的风险。
- 改动成本过高:现有大量
a.getX()等调用需改为a.getB().getX(),改动量巨大且无对应收益。
最终结论
你的方案更符合最佳实践,原因如下:
- 适配当前HQL构造的技术限制,工厂模式不可行时,直接复制属性是最合理的实现方式。
- 遵循单一职责与迪米特法则,降低A与B的耦合,后续维护成本更低。
- 保证业务代码的稳定性,外部调用A的代码无需关注B的内部结构。
针对同事的担忧,可通过引入Bean映射工具优化构造函数的手动赋值逻辑,同时在A的类注释中明确其业务载体的定位,避免后续维护误解。
内容的提问来源于stack exchange,提问作者pillow
相关产品推荐
相关产品推荐

