一、说明
再说依赖反转之前,首先谈下控制反转和依赖注入。是不是回想起很熟悉的一道面试题:知道spring吗?说说控制反转(IOC)和依赖注入(DI)。
二、控制反转(IOC)
1、概念
控制反转:Inversion Of Control,简称IOC。
2、代码分析
public class UserServiceTest {
public static boolean doTest() {
// ...
}
public static void main(String[] args) {
//这部分逻辑可以放到框架中
if (doTest()) {
System.out.println("Test succeed.");
} else {
System.out.println("Test failed.");
}
}
}
上面代码中其实就是硬编码,所有流程都由开发者自己控制,如果采取下面的这样一个框架来改造再看看效果
public abstract class TestCase {
public void run() {
if (doTest()) {
System.out.println("Test succeed.");
} else {
System.out.println("Test failed.");
}
}
// 类似于模板模式的模板方法
public abstract boolean doTest();
}
public class JunitApplication {
private static final List<TestCase> testCases = new ArrayList<>();
public static void register(TestCase testCase) {
testCases.add(testCase);
}
public static final void main(String[] args) {
for (TestCase case: testCases) {
case.run();
}
}
这个就相当于弄出个抽象模板方法doTest,使用者自己继承这个类去重写doTest方法,不再需要管main方法的执行流程。
public class UserServiceTest extends TestCase {
@Override
public boolean doTest() {
// ...
}
}
// 注册操作还可以通过配置的方式来实现,不需要程序员显示调用register()
JunitApplication.register(new UserServiceTest();
这就是对控制反转IOC最好的证明,用框架来驱动整个程序流程的执行,而不是需要每次都需要开发者自己控制主流程。Spring的框架不就是帮我们完成了这部分功能吗?所以,控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。
三、依赖注入(DI)
1、概念
依赖注入跟控制反转相反,它不是一种设计思想,而是一种能落地的具体的编码技巧。一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
2、代码分析
2.1、描述
Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用户。
2.2、非依赖注入的方式
// 非依赖注入实现方式
public class Notification {
private MessageSender messageSender;
public Notification() {
this.messageSender = new MessageSender(); //此处有点像hardcode
}
public void sendMessage(String cellphone, String message) {
//...省略校验逻辑等...
this.messageSender.send(cellphone, message);
}
}
public class MessageSender {
public void send(String cellphone, String message) {
//....
}
}
// 使用Notification
Notification notification = new Notification();
2.3、依赖注入的方式
// 依赖注入的实现方式
public class Notification {
private MessageSender messageSender;
// 通过构造函数将messageSender传递进来
public Notification(MessageSender messageSender) {
this.messageSender = messageSender;
}
public void sendMessage(String cellphone, String message) {
//...省略校验逻辑等...
this.messageSender.send(cellphone, message);
}
}
//使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);
其实就是将new的工作提取了出去,通过接收参数的方式去实例化,按照面向接口编程,还可以继续优化下,将MessageSender变成接口,有多种不同的实现。
public class Notification {
private MessageSender messageSender;
public Notification(MessageSender messageSender) {
this.messageSender = messageSender;
}
public void sendMessage(String cellphone, String message) {
this.messageSender.send(cellphone, message);
}
}
public interface MessageSender {
void send(String cellphone, String message);
}
// 短信发送类
public class SmsSender implements MessageSender {
@Override
public void send(String cellphone, String message) {
//....
}
}
// 站内信发送类
public class InboxSender implements MessageSender {
@Override
public void send(String cellphone, String message) {
//....
}
}
//使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);
回想下Spring框架,它就是完美的IOC+DI的一种具体落地方案。
四、依赖反转原则(DIP)
依赖反转:Dependency Inversion Principle,简称DIP。也称依赖倒置原则。简单来说就是高层模块不要依赖低层 模块,这之间通过抽象来实现相互依赖。比如Tomcat,我们写的web应用部署到Tomcat中就能实现web效果。Tomcat就属于高层模块,我们写的Web服务属于低层模块,Tomcat并没有依赖我们的Web模块,那怎么还能运行,这就是两者都共同依赖一个“抽象”模块,也就是Servlet规范。
其实这条原则还是针对框架的。开发过程中服务之间很少可能不产生依赖。