第二部分:理论五

第二部分:理论五

理论五

控制反转(IOC)

  • 控制反转的英文翻译是 Inversion OfControl,缩写为 IOC。
  • 文中举例,测试类 UserServiceTest 中一个测试方法 doTest(),程序员可以在业务代码的任何地方调用 doTest() 方法,所有的流程都由程序员来控制。
  • 以上代码如果我们抽象出一个框架,利用框架实现同样的功能。抽象类 TestCase 中一个抽象方法 doTest(),run() 方法中执行 doTest()。
  • 把这个测试框架引入工程中,只需要 UserServiceTest 类实现抽象类 TestCase,抽象方法 doTest() 就属于预留的扩展点,我们在 UserServiceTest 类中实现这个抽象方法 doTest(),填充具体测试代码即可。业务代码调用的时候只需要将 new UserServiceTest() 引入。
  • 以上就是典型的通过框架来实现“控制反转”的例子。框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。
  • 这里的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。
  • 控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。

流程都由程序员来控制的普通代码:

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 void 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();
		}
	}
}

只需要在框架预留的扩展点,也就是 TestCase 类中的 doTest() 抽象函数中,填充具体的测试代码就可以实现之前的功能了,完全不需要写负责执行流程的 main() 函数了。 具体的代码如下所示:

public class UserServiceTest extends TestCase {
	@Override
	public boolean doTest() {
		// ...
	}
}

// 注册操作还可以通过配置的方式来实现,不需要程序员显示调用 register()
JunitApplication.register(new UserServiceTest();

依赖注入(DI)

  • 依赖注入的英文翻译是 Dependency Injection,缩写为 DI。是一种具体的编码技巧。
  • 不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
  • 文中举例,Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用户。
    • 非依赖注入实现方式:
      • MessageSender 类中有个 send() 方法。
      • Notification 类的构造函数中 new MessageSender(),sendMessage 方法中调用 MessageSender 类中的 send() 方法。
      • 使用的时候直接 new Notification()。
    • 依赖注入实现方式:
      • MessageSender 类中有个 send() 方法,同上。
      • Notification 类的构造函数中以参数的形式传入类 MessageSender 的实例,sendMessage 方法中依旧调用 MessageSender 类中的 send() 方法。
      • 使用的时候,先 new MessageSender(),然后在 new Notification(messageSender) 时将实例化的 MessageSender 做为参数传入。
  • 以上例子可以看到,依赖注入的方式,主要是在构造函数中由直接实例化依赖类,改为将依赖类用参数的形式传递进来,这样就提高了代码的扩展性,我们可以灵活地替换依赖的类。
  • 继续优化还可以把 MessageSender 定义成接口,基于接口而非实现编程。不同的发送方式类(短信或站内信等)可以 implements 接口 MessageSender。

两种方式实现 Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用户,具体的实现代码:

// 非依赖注入实现方式
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();

// 依赖注入的实现方式
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 定义成接口,基于接口而非实现编程。改造后的代码如下所示:

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);

依赖注入框架(DI Framework)

  • 采用依赖注入实现的 Notification 类中,虽然我们不需要在类内部通过 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");
	}
}

依赖反转原则(DIP)

  • 关于 SOLID 原则的最后一个原则:依赖反转原则。英文翻译是 Dependency Inversion Principle,缩写为 DIP。
  • 这个原则用起来比较简单,但概念理解起来比较难。所以有前面这些铺垫学习。
  • 高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
  • 在调用链上,调用者属于高层,被调用者属于低层。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。
  • Tomcat 是运行 Java Web 应用程序的容器。Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat就是高层模块,Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系都依赖 Sevlet 规范。Servlet规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。
posted @ 2021-10-04 13:04  起床睡觉  阅读(35)  评论(0编辑  收藏  举报