依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
一、依赖注入的类别
1.1 Setter注入
Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
public interface IServiceClass
{
String ServiceInfo();
}
public class ServiceClassA : IServiceClass
{
public String ServiceInfo()
{
return "服务类A";
}
}
public class ServiceClassB : IServiceClass
{
public String ServiceInfo()
{
return "服务类B";
}
}
public class ClientClass
{
private IServiceClass _serviceImp;
public void Set_ServiceImp(IServiceClass serviceImp)
{
this._serviceImp = serviceImp;
}
public void ShowInfo()
{
Console.WriteLine(_serviceImp.ServiceInfo());
}
}
static void Main(string[] args)
{
IServiceClass serviceA = new ServiceClassA();
IServiceClass serviceB = new ServiceClassB();
ClientClass client = new ClientClass();
client.Set_ServiceImp(serviceA);
client.ShowInfo();
client.Set_ServiceImp(serviceB);
client.ShowInfo();
Console.ReadKey();
}
1.2 构造注入
构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
仅ClientClass 和 Content 有变化
public class ClientClass
{
private IServiceClass _serviceImp;
public ClientClass (IServiceClass serviceImp)
{
this._serviceImp = serviceImp;
}
public void ShowInfo()
{
Console.WriteLine(_serviceImp.ServiceInfo());
}
}
static void Main(string[] args)
{
IServiceClass serviceA = new ServiceClassA();
IServiceClass serviceB = new ServiceClassB();
ClientClass clientA = new ClientClass(serviceA);
clientA.ShowInfo();
ClientClass clientB = new ClientClass(serviceB);
clientB.ShowInfo();
Console.ReadKey();
}
1.3 依赖获取
依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。
二、 反射与依赖注入
回想上面Dependency Locate的例子,我们虽然使用了多态性和Abstract Factory,但对OCP贯彻的不够彻底。在理解这点前,朋友们一定要注意潜在扩展在哪里,潜在会出现扩展的地方是“新的组件系列”而不是“组件种类”,也就是说,这里我们假设组件就三种,不会增加新的组件,但可能出现新的外观系列,如需要加一套Ubuntu风格的组件,我们可以新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,并分别实现相应接口,这是符合OCP的,因为这是扩展。但我们除了修改配置文件,还要无可避免的修改FactoryContainer,需要加一个分支条件,这个地方破坏了OCP。依赖注入本身是没有能力解决这个问题的,但如果语言支持反射机制(Reflection),则这个问题就迎刃而解。
三、多态的活性与依赖注入
3.1 多态性的活性
高活多态性——指在客户类实例运行期间,服务类可能会改变的多态性。
中活多态性——指在客户类实例化后,服务类不会改变,但同一时间内存在的不同实例可能拥有不同类型的服务类。
低活多态性——指在客户类实例化后,服务类不会改变,且同一时间内所有客户类都拥有相同类型的服务类。
3.2 不同活性多态的依赖注入选择
一般来说,高活多态性适合使用Setter注入。因为Setter注入最灵活,也是唯一允许在同一客户类实例运行期间更改服务类的注入方式。并且这种注入一般由上下文环境通过Setter的参数指定服务类类型,方便灵活,适合频繁变化的高活多态性。
对于中活多态性,则适合使用Constructor注入。因为Constructor注入也是由上下文环境通过Construtor的参数指定服务类类型,但一点客户类实例化后,就不能进行再次注入,保证了其时间稳定性。
而对于低活多态性,则适合使用Dependency Locate并配合文件配置进行依赖注入,或Setter、Constructor配合配置文件注入,因为依赖源来自文件,如果要更改服务类,则需要更改配置文件,一则确保了低活多态性的时间和空间稳定性,二是更改配置文件的方式方便于大规模服务类替换。(因为低活多态性一旦改变行为,往往规模很大,如替换整个数据访问层,如果使用Setter和Construtor传参,程序中需要改变的地方不计其数)