因为所有的答案都强调理论,我想用一个例子优先的方法来证明:
假设我们正在构建一个应用程序,该应用程序包含在订单发出后发送SMS确认消息的功能。
我们将有两个类,一个负责发送SMS (SMSService),另一个负责捕获用户输入(UIHandler),我们的代码如下所示:
public class SMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
}
}
public class UIHandler
{
public void SendConfirmationMsg(string mobileNumber)
{
SMSService _SMSService = new SMSService();
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
Above implementation is not wrong but there are few issues:
-) Suppose On development environment, you want to save SMSs sent to a text file instead of using SMS gateway, to achieve this; we will end up changing the concrete implementation of (SMSService) with another implementation, we are losing flexibility and forced to rewrite the code in this case.
-) We’ll end up mixing responsibilities of classes, our (UIHandler) should never know about the concrete implementation of (SMSService), this should be done outside the classes using “Interfaces”. When this is implemented, it will give us the ability to change the behavior of the system by swapping the (SMSService) used with another mock service which implements the same interface, this service will save SMSs to a text file instead of sending to mobileNumber.
为了解决上述问题,我们使用接口,这些接口将由我们的(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)
{
/*implementation for sending SMS using gateway*/
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)
{
/*implementation for saving SMS to a file*/
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!");
}
}
我们已经实现了很大的灵活性,并在代码中实现了关注点分离,但是我们仍然需要在代码基础上做一些更改,以便在两个SMS服务之间切换。所以我们需要实现依赖注入。
为了实现这一点,我们需要对(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(); // dependency
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}