本文主要是介绍【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一. 控制反转(IOC)
- 二. 依赖注入(DI)
- 三. 依赖注入框架(DI Framework)
- 四. 依赖反转原则(DIP)
一. 控制反转(IOC)
通过一个例子来看一下,什么是控制反转。
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.");}}
}
改造为框架来实现同样的功能,如下:
// 将流程控制逻辑放到testcase类中,形成一个小的流程框架//1. 所有的逻辑类都继承这个类,抽象:以便将创建流程框架
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 {
//2. 通过组合的方式 ,统一执行所有的逻辑类private static final List<TestCase> testCases = new ArrayList<>();public static void register(TestCase testCase) {testCases.add(testCase);}// main在框架中写一遍,即可执行所有的逻辑类public static final void main(String[] args) {for (TestCase case: testCases) {case.run();}}
把这个简化版本的测试框架引入到工程中之后,我们只需要在框架预留的扩展点,也就是 TestCase 类中的 doTest() 抽象函数中,填充具体的测试代码就可以实现之前的功能了,完全不需要写负责执行流程的 main() 函数了。
具体的代码如下所示:
//
public class UserServiceTest extends TestCase {@Overridepublic boolean doTest() {// ... }
}// 通过传参的方式将自己的业务代码放到框架中,框架管理执行流程
JunitApplication.register(new UserServiceTest();
刚刚举的这个例子,就是典型的通过框架来实现“控制反转”的例子。有两点:
- 框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。
- 程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。
这里的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。
控制反转用于指导架构层面的设计
实际上,实现控制反转的方法有很多,除了刚才例子中所示的类似于模板设计模式的方法之外,还有马上要讲到的依赖注入等方法,所以,控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。
二. 依赖注入(DI)
依赖注入跟控制反转恰恰相反,它是一种具体的编码技巧。
用一句话来概括DI含义:
不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
举例说明:
// 1. 非依赖注入实现方式
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. 依赖注入的实现方式
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);
通过依赖注入的方式来将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖的类。
优化:基于接口编程
可以基于自己的需求来传递自己想通知的方式(短信、站内),提高代码灵活性和易拓展性。
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 {@Overridepublic void send(String cellphone, String message) {//....}
}// 站内信发送类
public class InboxSender implements MessageSender {@Overridepublic void send(String cellphone, String message) {//....}
}//使用Notification:基于自己的需求来传递自己想通知的方式,提高灵活性和拓展性
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);
实际上,你只需要掌握刚刚举的这个例子,就等于完全掌握了依赖注入。尽管依赖注入非常简单,但却非常有用,它是编写可测试性代码最有效的手段。
三. 依赖注入框架(DI Framework)
什么是“依赖注入框架”。继续借用刚刚的例子来解释。
上述例子还是存在需要手动new的情况
在采用依赖注入实现的 Notification 类中,虽然我们不需要用类似 hard code 的方式,在类内部通过 new 来创建 MessageSender 对象,但是,这个创建对象、组装(或注入)对象的工作仅仅是被移动到了更上层代码而已,还是需要我们程序员自己来实现。
public class Demo {public static final void main(String args[]) {MessageSender sender = new SmsSender(); //创建对象Notification notification = new Notification(sender);//依赖注入notification.sendMessage("13918942177", "短信验证码:2346");}
}
通过框架来自动化创建对象:
实际上,现成的依赖注入框架有很多,比如 Google Guice、Java Spring、Pico Container、Butterfly Container 等。
四. 依赖反转原则(DIP)
有了前面的基础,我们将依赖反转原则。
定义:
High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.
高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。
实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。我们拿 Tomcat 这个 Servlet 容器作为例子来解释一下。
Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。
- 按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。
- Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。
- Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。
参考:《设计模式之美》-- 王争
这篇关于【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!