SOLID原则:依赖反转原则(DIP)

SOLID:SOLID 原则并非单纯的1个原则,而是由5个设计原则组成,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则,SOLID 由5个设计原则的头一个字母组成。

单一职责原则和开闭原则的原理比较简单,但是,想要在实践中用好却比较难。而现在讲的依赖反转原则正好相反,原则用起来比较简单,但概念理解起来比较难。要理解依赖反转原则,我们需要先理解下面几个问题:

  • “依赖反转” 这个概念指的是 “谁跟谁” 的 “什么依赖” 被反转了?“反转” 两个字该如何理解?

  • “控制反转” 和 “依赖注入”,这两个概念跟 “依赖反转” 有什么区别和联系?它们说的是同一个事情吗?

  • Spring框架中的IOC跟这些概念又有什么关系?

上面的问题都是我们需要探讨的。

控制反转(IOC)

控制反转(Inversion Of Control,IOC),这里的 IOC 和 Spring 框架的 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.");
		}
	}
}

上面的代码中,所有的流程都是由程序员来控制。如果我们抽象除一个下面这样一个框架,再来看如何利用框架来实现同样的功能。

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 UserServiceTest extends TestCase {
	@Override
	public boolean doTest() { //... }
}

public class JunitApplication {
	private static final List<TestCase> testCases = new ArrayList<>();

	public static void register(TestCase testCase) {
		testCases.add(testCase);
	}

	public static void main(String[] args) {
		for (TestCase case : TestCase) {
			case.run();
		}
	}
}

修改后的版本,我们只需要在框架预留的扩展点 doTest() 抽象函数中填充具体的测试代码就可以实现之前的功能了,完全不需要写负责执行流程的 main()

这个例子就是典型的通过框架来实现“控制反转”的例子。框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程

这里的 “控制” 指的是对程序执行流程的控制,而 “反转” 指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员 “反转” 到了框架

实现控制反转的方法有很多,控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计

依赖注入(DI)

依赖注入(Dependency Injection,DI)跟控制反转恰恰相反,它是一种具体的编码技巧。

那到底什么是依赖注入呢?我们用一句话来概括就是:不通过 new 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用

还是通过一个例子来解释。

// 非依赖注入实现方式
public class Notification {
	private MessageSender messageSender;

	public Notification() {
		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();

// 使用依赖注入的实现方式
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);

通过依赖注入的方式将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖地类。上面的代码还有继续优化的空间,比如可以把 MessageSender 定义成接口,基于接口而非实现编程。

依赖注入框架(DI Framework)

上面的例子采用依赖注入的方式将 MessageSender 通过构造参数的方式传递给 Notification,而不需要用类似hard code的方式在 Notification 类内部通过 new 来创建 MessageSender 对象。但是,这个创建对象、组装(或注入)对象的工作仅仅是被移动到了更上层的代码而已,还是需要我们程序员自己来实现

public class Demo {
	public static final void main(String[] args) {
		MessageSender sender = new MessageSender(); // 创建对象
		Notification notification = new Notification(sender); // 依赖注入
		//...
	}
}

在实际的软件开发中,一些项目可能会涉及几十、上百、甚至几百个类,类对象的创建和依赖注入会变得非常复杂。如果这部分工作都是靠程序员自己写代码来完成,容易出错且开发成本也比较高。而对象创建和依赖注入的工作,本身跟具体的业务无关,我们完全可以抽象成框架来自动完成

这个框架就是 “依赖注入框架”。只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情

依赖注入框架有很多,比如Spring框架自己声称是控制反转容器(Inversion Of Control Container),只是控制反转容器这种表示是一种非常宽泛的描述,DI 依赖注入框架的表述更及具体、更有针对性。Spring 框架的控制反转主要是通过依赖注入来实现的,不过这点区别并不是很明显

依赖反转原则(DIP)

依赖反转原则(Dependency Inversion Principle,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.
高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)

所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。这条原则主要还是用来指导框架层面的设计,平时业务代码开发中,高层模块依赖低层模块是没有任何问题的。

比如 TomcatServlet,Tomcat 是运行 Java Web 应用程序的容器,我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个 “抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范

总结

1、什么是控制反转(IOC)?

控制反转其实就是程序执行流程本来由程序员处理,现在转由开发框架代为处理。流程的控制权从程序员“反转”到了框架。

2、什么是依赖注入(DI)、依赖注入框架(DI Framework)?

依赖注入(DI)一句话概括就是,不通过 new 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

依赖注入框架(DI Framework)就是提供了扩展点,只需要简单配置所需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。

3、如何理解依赖反转原则(DIP)?

这条原则主要还是用来指导框架层面的设计,高层模块和低层模块通过抽象来相互依赖,调用者属于高层模块,被调用者属于低层模块,即隔了一层抽象避免直接有依赖关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值