传统的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();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...省略其他代码
// 执行业务逻辑
userService.doService();
// ...返回页面视图
}
}
// 【2】UserServiceImpl类依赖UserDaoImpl类
public class UserServiceImpl implements UserService{
// 用于操作数据库的对象
private UserDao userDao = new UserDaoImpl();
@Override
public void doService() {
// ...省略业务逻辑代码
// 执行数据库操作
userDao.doUpdate();
// ...省略业务逻辑代码
}
}
public class UserDaoImpl implements UserDao{
@Override
public 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) => void
onDataCommitError: (data: LocalData) => void
onNewData: (data: ServerData) => void
}
class NetworkController {
constructor(options: INetworkControllerOptions) {
// 需要将各个接口实现保存下来
}
}
下面需要实现:
子组件通知父组件,并通知父组件:比如发生了 onDataCommitSuccess
那么需要将这些接口实现保存下来,并传入到各个对象内部分别在恰当的时机进行调用
interface ICallbackDependency {
onDataCommitSuccess?: (data: LocalData) => void
onDataCommitError?: (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);
});
}
}