深入理解IOC设计原则(上)
前置知识
面向对象设计模式(OOD)有五大准则(即SOLID原则)
依赖反转原则
该原则规定
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
问题
1.何为高层次模块,何为低层次模块?
高层次模块指的是那些实现较为抽象、功能较为宏观的模块,比如业务逻辑层或应用层;低层次模块指的是实现较为具体、功能较为细粒度的模块,比如数据访问层或服务层。
举一个例子:
现在有一个需求要求在已有项目中增加一个功能,绘制一个正方形。
通常我们拿到需求后会这样写:
class ChildDrawer extend SuperDrawer{ //.... public static drawSquare(startPos:Cartesian3,endPos:Cartesian3){ //pocess props to get new elements this.drawShap(elemen1,element2,....)//from super } //.... }这里的
SuperDrawer作为父级对象,功能较为抽象,层级较为宏观,因此就是高层次模块,ChildDrawer实现的功能离业务较近,功能粒度较细,因此就是低层次模块
2.为什么高层级模块和低层级模块都应该依赖于抽象接口
还是刚刚那个例子:
//顶层接口 //形状,仅包括一个绘制的方法 interface Shape{ draw:(...Cartesian3[])=>void; } class Shp implements Shape{ public draw(...Cartesian3[]){ //.... } }此时有高层次模块
class Drawer{ constructor(shp:Shp){ this.shp=shp } public drawShape(){ this.shp.draw(); } }此时有具体业务模块
class Square implements Shape{ public draw(start:Cartesian3,end:Cartesian3){ const pos1=start; const pos2=new Cartesian3(end.x,start.y); const pos3=new Cartesian3(start.x,end.y); const pos4=end; Tools.draw(pos1,pos2,pos3,pos4); } }例子
//绘制四边形 const square=new Square(); const drawService=new Drawer(Square); drawService.drawShape(); //绘制三角形 const triangle=new Triangle(); const drawService=new Drawer(Square);再看一个例子
假设我们正在开发一个简单的应用程序,其中有一个模块用于获取用户信息。我们可以先定义一个抽象接口
UserRepository,表示用户数据的存储和获取方式:interface UserRepository { getUser(id: number): Promise<User>; }然后我们再定义一个高层次的模块
UserService,它依赖于抽象接口UserRepository:class UserService { private userRepository: UserRepository; constructor(userRepository: UserRepository) { this.userRepository = userRepository; } async getUser(id: number): Promise<User> { return this.userRepository.getUser(id); } }最后,我们再定义一个具体的类
InMemoryUserRepository,它实现了抽象接口UserRepository,用于从内存中获取用户信息:class InMemoryUserRepository implements UserRepository { private users: User[]; constructor(users: User[]) { this.users = users; } async getUser(id: number): Promise<User> { const user = this.users.find((u) => u.id === id); if (!user) { throw new Error(`User with id ${id} not found`); } return user; } }现在我们可以使用这个程序来获取用户信息,而且可以方便地修改数据源,比如改为从数据库中获取用户信息,只需要定义一个新的类实现抽象接口
UserRepository,然后在定义UserService对象时传入即可:// 从内存中获取用户信息 const userRepository = new InMemoryUserRepository([ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]); const userService = new UserService(userRepository); // 从数据库中获取用户信息 const userRepository = new DatabaseUserRepository(); const userService = new UserService(userRepository);这个例子中,我们使用依赖倒置原则来设计程序,将低层次的模块
InMemoryUserRepository通过抽象接口UserRepository间接地依赖于高层次的模块UserService,降低了模块之间的耦合度(此时模块之间没有继承关系),使得程序更加灵活、可扩展和可维护。(如果还有问题,可以参考go语言接口的定义,语法简单的,go语言完全没有继承,全是这种组合方式)
3.如何理 “解抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。”
抽象接口是一个高层次的概念,它定义了系统的抽象行为。而具体实现则是一个低层次的概念,它实现了抽象接口定义的具体行为。
这个原则的意义在于,通过将具体实现依赖于抽象接口,可以将系统解耦,使得系统更加灵活、可扩展和可维护。具体实现可以通过实现抽象接口来实现系统的具体行为,这样就可以方便地替换具体实现,而不需要修改抽象接口的定义。同时,抽象接口也可以被多个具体实现所共享,这样可以提高代码的复用性和可维护性。
4.这样设计的好处
-
系统更柔韧:可以修改一部分代码而不影响其他模块。
-
系统更健壮:可以修改一部分代码而不会让系统崩溃。
-
系统更高效:组件松耦合,且可复用,提高开发效率。