基于SOLID原则:Mug类中Tea对象的创建方式哪种更优?
假设我们的程序中Mug类仅支持Tea对象,那么是在Mug类内部构造函数中实例化Tea对象更好,还是通过构造器或setter方法从外部传入Tea实例更好?
以下为两种实现示例:
第一种实现(内部实例化Tea):
class Mug { private Tea tea; public Mug(){ this.tea = new Tea(); } public boolean isFull(){ return this.tea.value != 10; } }
第二种实现(外部传入Tea):
class Mug { private Tea tea; public Mug(Tea tea){ this.tea = tea; } // public void setTea(Tea tea){ // this.tea = tea; // } public boolean isFull(){ return this.tea.value != 10; } }
调用示例:
public class Test { static void main(String[] args){ Mug mug = new Mug(); // 第一种实现的调用方式 // or Mug mug = new Mug(new Tea()); // 第二种实现的调用方式 } }
请问哪种实现方式更优?
毫无疑问第二种实现方式更优,完全贴合SOLID原则的核心思想,尤其是依赖倒置原则,我们来拆解下原因:
依赖倒置原则(DIP):高层模块(Mug)不应该依赖低层模块(Tea)的具体实现,而是依赖抽象。虽然这里Tea是具体类,但通过外部传入的方式,Mug不再负责Tea的创建,它只需要知道自己依赖Tea这个类型即可。如果未来Tea需要修改构造逻辑(比如需要传入参数
new Tea("green")),第一种实现需要修改Mug的构造函数,而第二种只需要在调用方调整,完全符合开闭原则(对扩展开放,对修改关闭)。单一职责原则(SRP):Mug的职责应该是“容纳Tea并判断是否满”,而不是“创建Tea”。第一种实现让Mug承担了额外的对象创建职责,违反了单一职责,当Tea的创建逻辑变化时,Mug也得跟着改,维护成本更高。
可测试性:如果要测试Mug的
isFull方法,第二种实现可以传入一个Mock的Tea对象(比如设置value为10或者其他值),而第一种实现只能依赖Tea的真实构造,测试灵活性大大降低。
这里额外提一下注释里的setter方法:如果业务逻辑允许Mug中途更换Tea,那保留setter是合理的;但如果Mug一旦创建就应该绑定固定的Tea,那只保留构造注入就足够了,避免不必要的状态变化。
总结下来,第二种实现把对象创建的职责交给了上层调用者,让Mug只专注于自己的核心功能,无论是扩展性、维护性还是可测试性都远优于第一种。
内容的提问来源于stack exchange,提问作者ImanX

