<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用TypeScript裝飾器實現(xiàn)一個簡單的依賴注入

          共 5708字,需瀏覽 12分鐘

           ·

          2020-09-24 00:23






          導語:在我們的代碼中,依賴就是兩個模塊間的一種關聯(lián)(如兩個類)——往往是其中一個模塊使用另外一個模塊去做些事情。使用依賴注入降低模塊之間的耦合度,使代碼更簡潔






          1

          開始之前

          什么是依賴(Dependency)

          ????????有兩個元素A、B,如果元素A的變化會引起元素B的變化,則稱元素B依賴(Dependency)于元素A。在類中,依賴關系有多種表現(xiàn)形式,如:一個類向另一個類發(fā)消息;一個類是另一個類的成員;一個類是另一個類的某個操作參數(shù),等等。

          為什么要依賴注入(DI)?

          ????????我們先定義四個Class,車,車身,底盤,輪胎。然后初始化這輛車,最后跑這輛車。


            假設我們需要改動一下輪胎(Tire)類,把它的尺寸變成動態(tài)的,而不是一直都是30。



            由于我們修改了輪胎的定義,為了讓整個程序正常運行,我們需要做以下改動:
            由此我們可以看到,僅僅是為了修改輪胎的構造函數(shù),這種設計卻需要修改整個上層所有類的構造函數(shù)!在軟件工程中,這樣的設計幾乎是不可維護的。
            所以我們需要進行?控制反轉(IoC),即上層控制下層,而不是下層控制著上層。我們用?依賴注入(Dependency Injection)?這種方式來實現(xiàn)控制反轉。所謂依賴注入,就是把底層類作為參數(shù)傳入上層類,實現(xiàn)上層類對下層類的“控制”。
            這里我們用構造方法傳遞的依賴注入方式重新寫車類的定義:

          ????這里我只需要修改輪胎類就行了,不用修改其他任何上層類。這顯然是更容易維護的代碼。不僅如此,在實際的工程中,這種設計模式還有利于不同組的協(xié)同合作和單元測試。


          2


          環(huán)境配置

          1.安裝?typescript?環(huán)境以及重要的 polyfill?reflect-metadata。

          2.在?tsconfig.json?中配置?compilerOptions

          {
          ? ?"experimentalDecorators": true, // 開啟裝飾器

          ? ?"emitDecoratorMetadata": true, // 開啟元編程}

          同時在入口文件引入?reflect-metadata。


          3


          預備知識

          Reflect

          簡介

          Proxy?與?Reflect?是?ES6?為了操作對象引入的?API,Reflect?的?API?和?Proxy?的?API?一一對應,并且可以函數(shù)式的實現(xiàn)一些對象操作。另外,使用?reflect-metadata?可以讓?Reflect?支持元編程。

          參考資料

          Reflect - JavaScript | MDN
          Metadata Proposal - ECMAScript

          類型獲取

          • 類型元數(shù)據(jù):design:type

          • 參數(shù)類型元數(shù)據(jù):design:paramtypes

          • 返回類型元數(shù)據(jù):design:returntype

          使用方法

          /** 

          * target: Object * propertyKey?: string | symbol */Reflect.getMetadata('design:type', target, propertyKey); // 獲取被裝飾屬性的類型

          Reflect.getMetadata("design:paramtypes", target, propertyKey); // 獲取被裝飾的參數(shù)類型

          Reflect.getMetadata("design:returntype", target, propertyKey); // 獲取被裝飾函數(shù)的返回值類型

          4


          正式開始

          編寫模塊

          首先寫一個服務提供者,作為被依賴模塊:

          // @services/log.tsclass LogService {

          ?public debug(...args: any[]): void {
          ? ?console.debug('[DEB]', new Date(), ...args);
          ?}

          ?public info(...args: any[]): void {
          ? ?console.info('[INF]', new Date(), ...args);
          ?}

          ?public error(...args: any[]): void {
          ? ?console.error('[ERR]', new Date(), ...args);

          ?}}

          然后我們再寫一個消費者:

          // @controllers/custom.tsimport LogService from '@services/log';


          class CustomerController { ?private log!: LogService;
          ?private token = "29993b9f-de22-44b5-87c3-e209f4174e39";

          ?constructor() {
          ? ?this.log = new LogService();
          ?}

          ?public main(): void {
          ? ?this.log.info('Its running.');

          ?}}

          現(xiàn)在我們看到,消費者在?constructor?構造函數(shù)內對?LogService?進行了實例化,并在?main?函數(shù)內進行了調用,這是傳統(tǒng)的調用方式。

          當?LogService?變更,修改了構造函數(shù),而這個模塊被依賴數(shù)很多,我們就得挨個找到引用此模塊的部分,并一一修改實例化代碼。

          架構設計

          1. 我們需要用一個?Map?來存儲注冊的依賴,并且它的?key?必須唯一。所以我們首先設計一個容器。

          2. 注冊依賴的時候盡可能簡單,甚至不需要用戶自己定義?key,所以這里使用?Symbol?和唯一字符串來確定一個依賴。

          3. 我們注冊的依賴不一定是類,也可能是一個函數(shù)、字符串、單例,所以要考慮不能使用裝飾器的情況。

          Container

          先來設計一個?Container?類,它包括?ContainerMap、set、get、has?屬性或方法。ContainerMap?用來存儲注冊的模塊,set?和?get?用來注冊和讀取模塊,has?用來判斷模塊是否已經(jīng)注冊。


          • set?形參?id?表示模塊?id,?value?表示模塊。


          • get?用于返回指定模塊?id?對應的模塊。


          • has?用于判斷模塊是否注冊。


          // @libs/di/Container.tsclass Container {

          ?private ContainerMap = new Map<string | symbol, any>();

          ?public set = (id: string | symbol, value: any): void => {
          ? ?this.ContainerMap.set(id, value);
          ?}
          ?
          ?public get = <T extends any>(id: string | symbol): T => {
          ? ?return this.ContainerMap.get(id) as T;
          ?}

          ?public has = (id: string | symbol): Boolean => {
          ? ?return this.ContainerMap.has(id);

          ?}}const ContainerInstance = new Container();export default ContainerInstance;

          Service

          現(xiàn)在實現(xiàn)?Service?裝飾器來注冊類依賴。

          // @libs/di/Service.tsimport Container from './Container';interface ConstructableFunction extends Function {

          ?new (): any;}// 自定義 id 初始化export function Service (id: string): Function;// 作為單例初始化export function Service (singleton: boolean): Function;// 自定義 id 并作為單例初始化export function Service (id: string, singleton: boolean): Function;export function Service (idOrSingleton?: string | boolean, singleton?: boolean): Function {

          ?return (target: ConstructableFunction) => {
          ? ?let _id;
          ? ?let _singleton;
          ? ?let _singleInstance;

          ? ?if (typeof idOrSingleton === 'boolean') {
          ? ? ?_singleton = true;
          ? ? ?_id = Symbol(target.name);
          ? ?} else {
          ? ? ?// 判斷如果設置 id,id 是否唯一
          ? ? ?if (idOrSingleton && Container.has(idOrSingleton)) {
          ? ? ? ?throw new Error(`Service:此標識符(${idOrSingleton})已被注冊.`);
          ? ? ?}

          ? ? ?_id = idOrSingleton || Symbol(target.name);
          ? ? ?_singleton = singleton;
          ? ?}

          ? ?Reflect.defineMetadata('cus:id', _id, target);

          ? ?if (_singleton) {
          ? ? ?_singleInstance = new target();
          ? ?}

          ? ?Container.set(_id, _singleInstance || target);

          ?};};

          Service?作為一個類裝飾器,id?是可選的一個標記模塊的變量,singleton?是一個可選的標記是否單例的變量,target?表示當前要注冊的類,拿到這個類之后,給它添加?metadata,方便日后使用。

          Inject

          接下來實現(xiàn)?Inject?裝飾器用來注入依賴。

          // @libs/di/Inject.tsimport Container from './Container';// 使用 id 定義模塊后,需要使用 id 來注入模塊

          export function Inject(id?: string): PropertyDecorator {
          ?return (target: Object, propertyKey: string | symbol) => {
          ? ?const Dependency = Reflect.getMetadata("design:type", target, propertyKey);

          ? ?const _id = id || Reflect.getMetadata("cus:id", Dependency);
          ? ?const _dependency = Container.get(_id);

          ? ?// 給屬性注入依賴
          ? ?Reflect.defineProperty(target, propertyKey, {
          ? ? ?value: _dependency,
          ? ?});

          ?};}

          5


          開始使用

          服務提供者

          log 模塊:

          // @services/log.tsimport { Service } from '@libs/di';@Service(true)class LogService {

          ?...}

          config 模塊:

          // @services/config.tsimport { Container } from '@libs/di';export const token = '29993b9f-de22-44b5-87c3-e209f4174e39';// 可在入口文件處調用以載入

          export default () => {

          ?Container.set('token', token);};

          消費者

          // @controllers/custom.tsimport LogService from '@services/log';class CustomerController {

          ?// 使用 Inject 注入
          ?@Inject()
          ?private log!: LogService;

          ?// 使用 Container.get 注入
          ?private token = Container.get('token');

          ?public main(): void {
          ? ?this.log.info(this.token);

          ?}}

          運行結果

          [INF] 2020-08-07T11:56:48.775Z 29993b9f-de22-44b5-87c3-e209f4174e39

          注意事項

          decorator?有可能會在正式調用之前初始化,因此?Inject?步驟可能會在使用?Container.set?注冊之前執(zhí)行(如上文的?config?模塊注冊和?token?的注入),此時可以使用?Container.get?替代。

          我們甚至可以讓參數(shù)注入在?constructor?形參里面,使用?Inject?直接在構造函數(shù)里注入依賴。當然這就需要自己下去思考了:

          constructor(@Inject() log: LogService) {

          ?this.log = log;}

          - END -





          分享前端好文,點亮?在看


          瀏覽 71
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色成人视频免费看 | 欧美视频天天干 | 日韩中文字幕网 | 成年性爱网站 | 天天天天射天天天搞天天要 |