由于所有的答案都强调理论,因此我希望首先通过示例演示:
假设我们正在构建一个应用程序,其中包含一项功能,即在订单发货后发送短信确认消息。
我们将有两个类,一个负责发送短信(SMSService),另一个负责捕获用户输入(UIHandler),我们的代码如下:
public class SMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
}
}
public class UIHandler
{
public void SendConfirmationMsg(string mobileNumber)
{
SMSService _SMSService = new SMSService();
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
上述实现并没有错误,但存在一些问题:
-) 假设在开发环境下,你想将发送的短信保存到文本文件而不是使用SMS网关,为了实现这一点,我们将不得不更改(SMSService)的具体实现,这样我们失去了灵活性并被迫在这种情况下重写代码。
-) 我们最终会混合类的职责,我们的(UIHandler)不应该知道(SMSService)的具体实现,这应该在使用“接口”的类之外完成。当这个被实现后,我们将有能力通过替换所使用的(SMSService)与另一个实现相同接口的模拟服务来改变系统行为,该服务将把短信保存到文本文件中而不是发送到手机号码。
为了解决上述问题,我们使用接口,它将由我们的(SMSService)和新的(MockSMSService)来实现,基本上新接口(ISMSService)将公开两个服务的相同行为,如下面的代码所示:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
那么我们将更改(SMSService)的实现以实现(ISMSService)接口:
public class SMSService : ISMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
Console.WriteLine("Sending SMS using gateway to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
现在我们可以使用相同的接口创建全新的模拟服务(MockSMSService),并采用完全不同的实现方式:
public class MockSMSService :ISMSService
{
public void SendSMS(string phoneNumber, string body)
{
SaveSMSToFile(phoneNumber,body);
}
private void SaveSMSToFile(string mobileNumber, string body)
{
Console.WriteLine("Mocking SMS using file to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
此时,我们可以在(UIHandler)中轻松地更改代码以使用服务的具体实现(MockSMSService),方法如下:
public class UIHandler
{
public void SendConfirmationMsg(string mobileNumber)
{
ISMSService _SMSService = new MockSMSService();
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
我们在代码中实现了很多灵活性并实现了关注点分离,但是我们仍然需要对代码进行更改,以在两个短信服务之间切换。因此我们需要实现依赖注入。
为了实现这一点,我们需要对(UIHandler)类构造函数进行更改,通过它传递依赖项。通过这样做,使用(UIHandler)的代码可以确定要使用哪种(ISMSService)的具体实现:
public class UIHandler
{
private readonly ISMSService _SMSService;
public UIHandler(ISMSService SMSService)
{
_SMSService = SMSService;
}
public void SendConfirmationMsg(string mobileNumber)
{
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
现在负责与类(UIHandler)通信的UI表单将要传递哪个接口实现(ISMSService)来消耗。这意味着我们已经颠倒了控制,(UIHandler)不再负责决定使用哪个实现,调用代码会决定。我们已经实现了控制反转原则,其中DI是其中的一种类型。
UI表单代码如下:
class Program
{
static void Main(string[] args)
{
ISMSService _SMSService = new MockSMSService();
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}