依赖,或者用英语更准确,dependency,是程序设计中不同对象或方法之间所必不可少的,我们的代码就是由不同的方法不同的对象相互调用组成的,因此,对于依赖,我们要做到心里有数,依赖是如何产生的,依赖的强弱关系是怎样的。依赖的强弱是指依赖者对被依赖对象的依赖程度,即替换被依赖对象所花费的代价程度如何,依赖程度越强,替换掉被依赖对象的代价就越大,方法越底层,难度越大,灵活性越差。
下面是我总结出来的引入依赖的几种形式,由强到弱依次排列
1.继承产生的依赖
SubClass对父类BaseClass的依赖是绝对的,编译时即确定的,这种依赖无法被替换,不能在运行时让SubClass继承另外一个父类
public class SubClass : BaseClass {}
解决办法:由于这种继承关系在代码写下的那一刻已经确定,因此无法通过任何方法来替换这种继承关系,因此建议在程序设计时,首先考虑继承关系是否有必要,能否用组合关系代替继承关系。如果要override父类的方法,则必须继承,如果只用到父类的某些方法,建议用组合替代继承
2.由new关键字创建对象产生的依赖
下面的例子中,通过new关键字引入了对Car类的依赖,这种依赖也是无法被替换的,并且这种依赖的形式较为隐蔽,无法通过看类的定义或者方法签名直接看出来,需要避免。
public void RunACar() { Car a = new Car(); a.Run(); } or private Car aCar = new Car();
解决办法:尽量不要在方法体或者类内部用new关键字生成对象,应该通过方法参数或者依赖注入或者构造注入来处理这种依赖。
方法参数: public void RunACar(Car car) { a.Run(); } 依赖注入: public class A { [Dependency] private Car aCar; public void RunACar() { a.Run(); } } 构造注入: public class A { private Car aCar; public A(Car car) { this.aCar = car; } public void RunACar() { aCar.Run(); } }
3.静态方法
静态方法也是一种在编译时已经确定的依赖,无法被替换。
public class Car { public static void Run() {} } public void RunACar() { Car.Run() }
解决办法:
静态方法应该设计为无状态的,无论调用者是谁,只要输入一致,输出的结果应该是相同的。如果是有状态的,考虑把静态方法设计为实例方法,并参照第二条由new关键字产生的依赖的解决方法。
4.类数据成员的类型
下面的例子中AService类对Car类的依赖,Car类是数据成员carList的类型,这种依赖是可以在运行时通过构造注入或依赖注入用Car类的子类型替换Car。
public class AService : IAService { private Car aCar; }
运行时替换:
假设有个子类Track继承了Car类 public class Track: Car {} 依赖注入替换: public class AService : IAService { [Dependency(typeof(Track))] private Car aCar; } 构造注入替换: public class AService : IAService { private Car aCar; public AService(Car car) { aCar = car; } } var car = new Track(); var service = new AService(car);
5.方法参数引入的依赖(构造方法或成员方法)
这种方式提供了更大的灵活性,可以在运行时动态替换Car的类型
public void RunACar(Car car) { a.Run(); }
我们应该尽量选择依赖程度较小的方式来实现代码,以换取更大的灵活性,在日常写代码的时候多注意这些细节,才能更加精进自己的编程能力和技巧。今天就到这吧。