传统的MVC
既然解释了依赖注入与控制反转,是java常用的高级设计思想
现在看看在这个思想出来之前,大家是怎么组织代码的,又遇到了什么问题

业界普遍按这种分层方式组织代码,其核心思想是职责分离。层次越低复用程度越高,比如一个 DAO 对象往往会被多个 Service 对象使用,一个 Service 对象往往也会被多个 Controller 对象使用
controller对依赖的service类是如何管理的?
controllerA需要调用serviceA、serviceB类的xx方法,就是在controller里实例化各个service
上层调用下一层时,必然会持有下一层的对象引用,即成员变量。
弊端是什么?
1、只是代码复用了,但是资源没有复用。
每一个链路都创建了同样的对象,造成了极大的资源浪费
本应多个 Controller 复用同一个 Service,多个 Service 复用同一个 DAO。现在变成了一个 Controller创建多个重复的 Service,多个 Service 又创建了多个重复的 DAO,从倒三角变成了正三角。
2、变化的代价太大
2.1 修改依赖的类
假设有 10 个 Controller 依赖了 UserService,最开始实例化的是 UserServiceImpl,后面需要换一个实现类 OtherUserServiceImpl,我就得逐个修改那 10 个 Controller,非常麻烦
2.2 创建类和配置类麻烦
配置可能会随着业务需求的变化经常更改,这时候你就需要修改每一个依赖该组件的地方,牵一发而动全身
- 创建了许多重复对象,造成大量资源浪费;
- 更换实现类需要改动多个地方;
- 创建和配置组件工作繁杂,给组件调用方带来极大不便。
透过现象看本质,这些问题的出现都是同一个原因:组件的调用方参与了组件的创建和配置工作。
before
一般将视图控制、业务逻辑和数据库操作分别抽离出来单独形成一个类,这样各个职责就非常清晰且易于复用和维护
controller: UserServlet 依赖 UserServiceImpl类@WebServlet("/user")public class UserServlet extends HttpServlet {// 用于执行业务逻辑的对象 【1】private UserService userService = new UserServiceImpl();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// ...省略其他代码// 执行业务逻辑userService.doService();// ...返回页面视图}}// 【2】UserServiceImpl类依赖UserDaoImpl类public class UserServiceImpl implements UserService{// 用于操作数据库的对象private UserDao userDao = new UserDaoImpl();@Overridepublic void doService() {// ...省略业务逻辑代码// 执行数据库操作userDao.doUpdate();// ...省略业务逻辑代码}}public class UserDaoImpl implements UserDao{@Overridepublic void doUpdate() {// ...省略JDBC代码}}
demo2

class NetworkController {constructor(options: INetworkControllerOptions) {this.init();}init() {this.versionManager = new VersionManager(); // 版本管理this.connectLayer = new ConnectLayer(); // 连接层this.netWorkManager = new NetWorkManager(); // 网络状态管理this.taskListManager = new TaskListManager(this.versionManager); // 任务队列管理this.dataListManager = new DataListManager(); // 待提交数据队列this.sendDataController = new SendDataController(this.taskListManager,this.dataListManager); // 发送数据控制器this.receiveDataController = new ReceiveDataController(this.taskListManager,this.dataListManager,this.netWorkManager); // 接受数据控制器}}
状态变更这些也是通过注册回调的方式进行设计的
父组件NetworkController
interface INetworkControllerOptions {// 其他参数onNetworkChange: (newStatus: NetWorkStatus) => void,onDataCommitSuccess: (data: LocalData) => voidonDataCommitError: (data: LocalData) => voidonNewData: (data: ServerData) => void}class NetworkController {constructor(options: INetworkControllerOptions) {// 需要将各个接口实现保存下来}}
下面需要实现:
子组件通知父组件,并通知父组件:比如发生了 onDataCommitSuccess
那么需要将这些接口实现保存下来,并传入到各个对象内部分别在恰当的时机进行调用
interface ICallbackDependency {onDataCommitSuccess?: (data: LocalData) => voidonDataCommitError?: (data: LocalData) => void}interface ITaskListManagerDependency {addTask: (task: BaseTask) => void;}interface IDataListManagerDependency {pushData: (data: LocalData) => void;shiftData: () => LocalData;}class SendDataController {constructor(taskListManagerDependency: ITaskListManagerDependency,dataListManagerDependency: IDataListManagerDependency,# 在初始化的时候需要通过注入的方式传进来callbackDependency: ICallbackDependency,) {}handleDataCommitSuccess(data: LocalData) {try {// 该函数还可能为空# 子组件触发父组件的回调this.callbackDependency.onDataCommitSuccess?.(data);} catch (error) {// 使用的时候还需要注意异常问题}}}
业务组件怎么使用?
也要初始化接口
const netWorkLayer = new NetworkController({// 其他参数otherOptions: {},onNetworkChange: () {// 网络状态变更处理},onDataCommitSuccess: () {// 提交数据成功处理},onDataCommitError: () {// 提交数据失败处理},onNewData: () {// 服务端新数据处理},})
所以问题两个:
1、controller层:需要在constructor的时候实例化全部对象,进行统一管控
适合自上而下的
没有一个 适配层,比如上层改个配置下面很容易就切换了
2、基于总管理器的问题,所以组件之间通信方式:底层一步步往上传
具体实现:外部开放个回调接口,子组件去触发这个异步接口,并把函数传到外面
after
依赖注入原理,重要的设计思想是: 面向接口编程
(比面向测试更细粒度,比面向对象编程也更松散化
控制反转,是指对象的创建和配置的控制权从调用方转移给容器。
有了 IoC 容器,我们可以将对象交由容器管理,交由容器管理后的对象称之为 Bean。调用方不再负责组件的创建,要使用组件时直接获取 Bean 即可:
使用依赖倒置进行依赖解耦
依赖倒置原则有两个,其中包括了:
- 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
- 抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。
以SendDataController为例:
依赖TaskListManager其实主要是依赖的添加任务的接口addTask()
依赖DataListManager则是依赖添加数据pushData()、取出数据shiftData(),
转换为代码:
interface ITaskListManagerDependency {addTask: (task: BaseTask) => void;}interface IDataListManagerDependency {pushData: (data: LocalData) => void;shiftData: () => LocalData;}class SendDataController {constructor(taskListManagerDependency: ITaskListManagerDependency,dataListManagerDependency: IDataListManagerDependency) {// 相关依赖可以保存起来,在需要的时候使用}}
如果项目中有完善的依赖注入框架,则可以使用项目中的依赖注入体系。
实际上,我们可以给每个对象提供自身的接口描述,
这样其他对象中可以直接import同一份接口也是可以的,管理和调整会比较方便。
所以before的demo2改造为:
如果项目中有完善的依赖注入框架,则可以使用项目中的依赖注入体系。
在我们这个例子里,总控制器充当了依赖注入的控制角色,
而具体其中的各个对象之间,实现了基于抽象接口的依赖,成功了进行了解耦。
依赖注入在大型项目中比较常见,对于各个模块间的依赖关系管理很实用。
除了初始化相关,总控制器的职责还包括对业务层提供接口和事件监听,
其中接口中会依赖具体职责对象的协作:
使用事件驱动进行依赖解耦
子组件,向上传递_onDataCommitSuccess事件
class SendDataController {private readonly _onDataCommitSuccess = new Emitter<LocalData>();readonly onDataCommitSuccess: Event<LocalData> = this._onDataCommitSuccess.event;constructor(taskListManagerDependency: ITaskListManagerDependency,dataListManagerDependency: IDataListManagerDependency,// 在初始化的时候需要通过注入的方式传进来callbackDependency: ICallbackDependency,) {}handleDataCommitSuccess(data: LocalData) {this._onDataCommitSuccess.fire(data);}}
class NetworkController {// 提供的事件private readonly _onNetworkChange = new Emitter<NetWorkStatus>();readonly onNetworkChange: Event<NetWorkStatus> = this._onNetworkChange.event;private readonly _onDataCommitSuccess = new Emitter<LocalData>();readonly onDataCommitSuccess: Event<LocalData> = this._onDataCommitSuccess.event;private readonly _onNewData = new Emitter<ServerData>();readonly onNewData: Event<ServerData> = this._onNewData.event;constructor(options: INetworkControllerOptions) {this.init();this.initEvent();}initEvent() {// 监听 SendDataController 的事件,并触发自己的事件# 父组件是怎么拿到 this.sendDataController 这个实例的?// 也是init里?this.sendDataController.onDataCommitSuccess(data => {this._onDataCommitSuccess.fire(data);});}}
