<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裝飾器實(shí)現(xiàn)一個(gè)簡單的依賴注入

          共 5902字,需瀏覽 12分鐘

           ·

          2020-10-01 16:08





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





          作者:charryhuang

          原文地址:https://mp.weixin.qq.com/s/waBAJgYaOJQu2qdcbgmi1A

          1

          開始之前

          什么是依賴(Dependency)

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

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

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


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



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

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


          2


          環(huán)境配置

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

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

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

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

          同時(shí)在入口文件引入?reflect-metadata。


          3


          預(yù)備知識(shí)

          Reflect

          簡介

          Proxy?與?Reflect?是?ES6?為了操作對(duì)象引入的?API,Reflect?的?API?和?Proxy?的?API?一一對(duì)應(yīng),并且可以函數(shù)式的實(shí)現(xiàn)一些對(duì)象操作。另外,使用?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


          正式開始

          編寫模塊

          首先寫一個(gè)服務(wù)提供者,作為被依賴模塊:

          // @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);

          ?}}

          然后我們?cè)賹懸粋€(gè)消費(fèi)者:

          // @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)在我們看到,消費(fèi)者在?constructor?構(gòu)造函數(shù)內(nèi)對(duì)?LogService?進(jìn)行了實(shí)例化,并在?main?函數(shù)內(nèi)進(jìn)行了調(diào)用,這是傳統(tǒng)的調(diào)用方式。

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

          架構(gòu)設(shè)計(jì)

          1. 我們需要用一個(gè)?Map?來存儲(chǔ)注冊(cè)的依賴,并且它的?key?必須唯一。所以我們首先設(shè)計(jì)一個(gè)容器。

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

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

          Container

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


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


          • get?用于返回指定模塊?id?對(duì)應(yīng)的模塊。


          • has?用于判斷模塊是否注冊(cè)。


          // @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)在實(shí)現(xiàn)?Service?裝飾器來注冊(cè)類依賴。

          // @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 {
          ? ? ?// 判斷如果設(shè)置 id,id 是否唯一
          ? ? ?if (idOrSingleton && Container.has(idOrSingleton)) {
          ? ? ? ?throw new Error(`Service:此標(biāo)識(shí)符(${idOrSingleton})已被注冊(cè).`);
          ? ? ?}

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

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

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

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

          ?};};

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

          Inject

          接下來實(shí)現(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


          開始使用

          服務(wù)提供者

          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';// 可在入口文件處調(diào)用以載入

          export default () => {

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

          消費(fèi)者

          // @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);

          ?}}

          運(yùn)行結(jié)果

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

          注意事項(xiàng)

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

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

          constructor(@Inject() log: LogService) {

          ?this.log = log;}

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長指北,回復(fù)「1」加入Node進(jìn)階交流群!「在這里有好多 Node 開發(fā)者,會(huì)討論 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。


          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 99
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  色五月乱伦小说 | 亲子乱一区二区 | 4438成人网站 | 91成人篇| 性色在线|