最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript 的 IoC、IoC Containers、DI、DIP和Reflect

    正文概述 掘金(DjangoXiang)   2020-12-30   585

    基本概念

    来自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原则重构代码,那么我们就必须将低层类FingerPrintSensorPasswordKeyboardCameraRFIDReader,抽象成一个抽象类,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;
      }
    }
    

    JavaScript 的 IoC、IoC Containers、DI、DIP和Reflect

    两者差别

    目的是为了解偶而产生的设计原则是IoC和DIP相同点,两者区别是:

    • IoC从程序流控制方面切入,解除依赖于被依赖的关系
    • DIP从对类的设计上面切入,处理调用与被调用对象的依赖关系,增加一个中间部分(抽象类)

    两者本质上不矛盾,大部分内容是相同的。

    JavaScript 的 IoC、IoC Containers、DI、DIP和Reflect

    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天时间被设计出来用在网页前端做一些简单操作的那个语言。

    如果有不对的地方,非常感谢和欢迎同学们指出。


    起源地下载网 » JavaScript 的 IoC、IoC Containers、DI、DIP和Reflect

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元