基本概念
来自WiKi对IoC的解释
简单的概括一下来说:传统情况下,开发者写程序调用框架(其他程序员写的);而IoC则是让框架调用开发者的代码。IoC是一种法则(或称之为原则),我的理解是它并不属于某一种开发模式(Pattern),因为它是一个比较普世的概念。然而,开发模式则是某个法则的实现(或者说最佳实践)。例如,依赖注入(Dependency Injection)就是一种IoC的实现,工厂模式(Factory,或者抽象工厂)也是一种实现。
另外IoC的一大应用领域是TDD(测试驱动开发),这是基于IoC理念之上的一种测试方法。
来自WiKi对DIP对解释
依赖反转原则是SOLID的原则之一(其中的D)。其解偶强调高层类(调用方)不依赖于低层(low-level)类(被调用方)。
案例代码
比如:大楼门禁系统有指纹锁、密码锁、刷脸锁和磁卡锁,一个门禁系统包含、一扇门,门上有一把锁,一个验证机构。比较传统的做法(强耦合),将会如下代码:
// 锁机构
class Locker {}
// 房门
class Door {}
// 指纹传感器
class FingerPrintSensor {
readFingerPrint() {}
}
// 键盘
class PasswordKeyboard {
readInput() {}
}
// 摄像头
class Camera {
takePicture() {}
}
// 读卡器
class RFIDReader {
readCard() {}
}
// 指纹门禁
export class FingerPrintGuard {
fingerPrintSensor = new FingerPrintSensor();
locker = new Locker();
docker = new Door();
}
// 密码门禁
export class PasswordGuard {
keyboard = new PasswordKeyboard();
locker = new Locker();
docker = new Door();
}
// 刷脸门禁
export class FacialGurad {
camera = new Camera();
locker = new Locker();
docker = new Door();
}
// 磁卡门禁
export class IDGuard {
reader = new RFIDReader();
locker = new Locker();
docker = new Door();
}
利用DIP原则重构代码,那么我们就必须将低层类FingerPrintSensor
,PasswordKeyboard
,Camera
,RFIDReader
,抽象成一个抽象类,Guarder
。(因JS不支持abstract关键词,用普通类加上抛异常来编写代码),解除高层类*Guard
对其的依赖。
export class Guarder {
getKeyInfo() {
throw "实现验证输入";
}
}
再将上述四个类作扩展这个抽象类
// 指纹传感器
class FingerPrintSensor extends Guarder {
readFingerPrint() {}
getKeyInfo = this.readFingerPrint;
}
// 键盘
class PasswordKeyboard extends Guarder {
readInput() {}
getKeyInfo = this.readInput;
}
// 摄像头
class Camera extends Guarder {
takePicture() {}
getKeyInfo = this.takePicture;
}
// 读卡器
class RFIDReader extends Guarder {
readCard() {}
getKeyInfo = this.readCard;
}
此时,高层门禁类会改为如下:
class Guard {
guarder = new Guarder();
locker = new Locker();
docker = new Door();
}
因为JS是弱类型语言,如果用TS或者其他高级语言则会更容易理解;通过上述代码不难发现,我们已经将门禁和门禁特性拆开了。同理,以IoC原则的依赖注入(DI)方式来理解,那么少许改动Guard(typescript)
class Guard {
locker = new Locker();
docker = new Door();
constructor(guarder:Guarder) {
this.guarder = guarder;
}
}
两者差别
目的是为了解偶而产生的设计原则是IoC和DIP相同点,两者区别是:
- IoC从程序流控制方面切入,解除依赖于被依赖的关系
- DIP从对类的设计上面切入,处理调用与被调用对象的依赖关系,增加一个中间部分(抽象类)
两者本质上不矛盾,大部分内容是相同的。
IoC的实现模式(pattern)
用DI的方式实现IoC
上面的例子已经简单的利用了构造函数对被依赖的对象(类)进行了注入操作,还可以利用一些其他的方式进行注入操作,比如专门使用一个绑定函数。还可以利用现在比较流行且优雅的方式装饰器来注入,有兴趣的同学可以参考此前我写的一篇文章。
class Guard {
@Guarder // 属性装饰器
guarder = {};
locker = new Locker();
docker = new Door();
}
利用这种开发模式,底层的框架可以设计出诸多装饰器,而具体的逻辑则由调用方面的代码处理。
其他的方式
服务定位模式(SL),在本是弱类型的JS中用的比较少(但是Java或其他高级语言,特别是大型框架中相对较多)。其实现原理大致上是将依赖的类装入到一个词典中(或Map),然后利用一个配置文件或者常量来在需要调用(依赖)时候利用服务加载器来实现引用过程。以此来实现一定程度解偶。我们还是拿门禁的案例来说明:
// 服务定位器(类)
class ServiceLocator {
static sInstance = {};
static load(arg) {
ServiceLocator.sInstance = arg;
}
services = {};
loadService(key, service) {
this.services[key] = service;
}
static getService(key) {
console.log(key, ServiceLocator.sInstance.services[key]);
return ServiceLocator.sInstance.services[key];
}
}
// 门禁,可以根据配置加载
class Guard {
guarder = ServiceLocator.getService("Camera");
locker = new Locker();
docker = new Door();
}
// 注册服务
(function () {
let locator = new ServiceLocator();
locator.loadService("FingerPrintSensor", new FingerPrintSensor());
locator.loadService("PasswordKeyboard", new PasswordKeyboard());
locator.loadService("Camera", new Camera());
locator.loadService("RFIDReader", new RFIDReader());
ServiceLocator.load(locator);
})();
以上代码,让开发者可以依据某种配置来构建类;另外,从自动化测试方面来讲,提供了一种新的方法。(TDD)
IoC Container
IoC容器,指的是一种设计框架(Framework),inversify在Javascript和TypeScript中被应用的比较有名。它解决了面向对象的开发过程中,各种错综复杂的依赖关系的处理。有兴趣的同学可以前往官网详细了解。
反射
来自WiKi对反射的解释
分层的开发模式基本上都会用到反射方法,IoC可以看作为是一种分层的开发模式。利用反射,会让程序更为优雅和整洁。JS中,关于反射主要用到三个对象的相关静态方法,Object、Reflect和Proxy,对被操作的类或者实例进行扩展、修改操作。
代码案例
为了方便演示,我利用TypeScript来举例:
import "reflect-metadata";
const GUARDER = Symbol("Guarder");
type Constructor<T = any> = new (...args: any[]) => T;
const __KindOfGuarder: Function[] = [];
const Injectable = (): ClassDecorator => (target) => {};
const KindOfGuarder = (name): ClassDecorator => {
return (target) => {
__KindOfGuarder.push(target);
Reflect.defineMetadata(GUARDER, name, target);
};
};
class Guarder {
getKeyInfo() {
throw "实现验证输入";
}
}
@KindOfGuarder("finger")
class FingerPrintSensor extends Guarder {
readFingerPrint() {
console.log("读取指纹");
}
getKeyInfo = this.readFingerPrint;
}
@KindOfGuarder("password")
class PasswordKeyboard extends Guarder {
readInput() {
console.log("读取密码");
}
getKeyInfo = this.readInput;
}
@Injectable()
class Guard {
constructor(public readonly guarder: Guarder) {}
verifyMethod() {
this.guarder.getKeyInfo();
}
}
const Factory = <T>(target: Constructor<T>, name): any => {
const providers = Reflect.getMetadata("design:paramtypes", target);
const index = __KindOfGuarder.findIndex(
(guarder) => Reflect.getMetadata(GUARDER, guarder) === name
);
if (index >= 0) {
let instance = new (<Constructor>__KindOfGuarder[index])();
if (
providers &&
providers.length === 1 &&
instance instanceof providers[0]
) {
return new target(instance);
}
} else {
throw "没有找到可以构造的类型";
}
};
Factory(Guard, "finger").verifyMethod();
Factory(Guard, "password").verifyMethod();
(20和28行)先利用装饰器,(10行)将扩展类一个个注册。(61和62行)使用工厂模式,将调用方面和被调用组合起来,并执行相关方法。
总结
IoC、DIP都是最先由Robert C. Martin大叔提出的,围绕其提出的SOLID编程原则。目的是解除程序之间强耦合性,以适应可扩展和可修改的维护需求。前端项目(也可能是Node后端)有着越来越大的发展趋势,但凡使用OOP的编程(思想)并在大规模项目面前,IoC和DIP都是绕不过去的技能树,非常有必要牢固地掌握。
配合JS中的Reflect对象,再外挂上reflect-metadata以及TypeScript这个强类型超集,使得JS的编程越来越面相与规模化。已经完全不是那个仅用了10天时间被设计出来用在网页前端做一些简单操作的那个语言。
如果有不对的地方,非常感谢和欢迎同学们指出。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!