Angular中的依赖注入与设计模式的总结

本文将设计模式和Angular 依赖注入DI结合,希望帮助大家更好地理解设计模式在DI中的运用。

1. 介绍DI

依赖注入(Dependency Injection)是现代软件开发中一个重要的概念,它可以帮助我们更好地管理组件之间的依赖关系,并提高代码的可测试性、可维护性和可扩展性。

在软件开发中,组件通常需要依赖其他组件或服务来完成特定的功能。传统的做法是在组件内部直接实例化依赖项,但这样会导致组件与具体的依赖项紧密耦合,难以进行测试和扩展。

依赖注入解决了这个问题,它是一种将依赖项从消费者解耦并委托给外部提供的机制。通过依赖注入,组件不需要关心如何创建或获取依赖项的实例,而是将这个任务交给外部的依赖注入系统来完成。

2. Angular中的DI机制

在Angular中,依赖注入(DI)用于管理组件之间的依赖关系和提供可重用的服务。有几个关键概念:

2.1 注入器(Injector)

注入器是Angular框架中负责实现依赖注入的核心组件。在本节中,我们将深入探讨注入器的工作原理和角色:

  • 注入器的层次结构:Angular中的注入器是层次结构的,每个组件都有自己的注入器,这些注入器通过树状结构相互关联。
  • 依赖解析过程:当组件需要使用某个依赖项时,注入器会通过递归方式在注入器树中进行依赖项的查找和解析。 详细的层级参考:
    www.youtube.com/watch?v=uVG…

Angular中的依赖注入与设计模式的总结

  • 提供器注册:提供器用于告诉注入器如何创建依赖项的实例。我们将介绍提供器的不同类型(类提供器、值提供器和工厂提供器)以及如何使用提供器来注册依赖项。

2.2 提供器(Providers)

提供器是定义依赖项及其如何创建的对象的一种方式。在本节中,我们将更深入地探讨提供器的概念和用法:

  • 类提供器:通过将类作为提供器来注册依赖项,Angular会使用类的构造函数来创建依赖项的实例。
import { Injectable } from '@angular/core';

@Injectable()
class DataService {
  // 数据服务的实现代码...
}

// 在模块或组件的提供器数组中注册类提供器
providers: [DataService]

  • 值提供器:通过将值作为提供器来注册依赖项,Angular会直接使用提供的值作为依赖项的实例。
import { InjectionToken } from '@angular/core';

// 创建注入令牌
const API_URL = new InjectionToken<string>('apiUrl');

// 在模块或组件的提供器数组中注册值提供器
providers: [{ provide: API_URL, useValue: 'https://api.example.com' }]

  • 工厂提供器:通过将工厂函数作为提供器来注册依赖项,Angular会使用工厂函数的返回值作为依赖项的实例。
import { Injectable } from '@angular/core';

@Injectable()
class Logger {
  // Logger服务的实现代码...
}

// 创建工厂函数
function loggerFactory() {
  return new Logger();
}

// 在模块或组件的提供器数组中注册工厂提供器
providers: [{ provide: Logger, useFactory: loggerFactory }]

  • 多重提供器:当有多个提供器注册同一个依赖项时,我们可以使用多重提供器来指定不同的实现。
import { Injectable } from '@angular/core';

@Injectable()
class PluginService {
  // 插件服务的实现代码...
}

@Injectable()
class ExtensionService {
  // 扩展服务的实现代码...
}

// 在模块或组件的提供器数组中注册多个提供器
providers: [PluginService, ExtensionService]

2.3 注入令牌(Injection Tokens)

注入令牌用于标识和访问依赖项。在本节中,我们将更详细地介绍注入令牌的使用:

  • 默认注入令牌:当我们使用类作为提供器注册依赖项时,Angular会使用类本身作为默认的注入令牌。
  • 自定义注入令牌:有时我们需要使用自定义的注入令牌来标识依赖项。我们将介绍如何创建自定义注入令牌,并在提供器中使用它们。
    自定义注入令牌例子:
import { InjectionToken } from '@angular/core';

// 创建注入令牌
const APP_CONFIG = new InjectionToken<AppConfig>('appConfig');

// 定义接口或类来描述注入令牌所代表的依赖项
interface AppConfig {
  apiUrl: string;
  apiKey: string;
}

// 在模块或组件的提供器数组中注册自定义注入令牌
providers: [
  { provide: APP_CONFIG, useValue: { apiUrl: 'https://api.example.com', apiKey: '123456789' } }
]

在上面的示例中,我们首先使用InjectionToken类创建了一个名为APP_CONFIG的自定义注入令牌。然后,我们定义了一个接口AppConfig来描述该注入令牌所代表的依赖项的结构。接下来,在模块或组件的提供器数组中,我们使用provide属性指定了注入令牌,并使用useValue属性提供了具体的依赖项实例。

通过这样的设置,我们可以将APP_CONFIG注入到需要的组件或服务中,以访问其中的依赖项:

import { Component, Inject } from '@angular/core';

@Component({...})
class MyComponent {
  constructor(@Inject(APP_CONFIG) private config: AppConfig) {
    console.log(config.apiUrl); // 输出:https://api.example.com
    console.log(config.apiKey); // 输出:123456789
  }
}

在上面的示例中,我们在组件的构造函数中使用@Inject装饰器将APP_CONFIG注入到config参数中。然后,我们可以通过config参数访问注入的依赖项的属性。

通过使用自定义注入令牌,我们可以更灵活地标识和访问依赖项,并在需要的地方进行注入。这使得我们可以轻松地切换不同的依赖项实现,同时保持组件的解耦性和可测试性。

2.4 特殊的注入情况

除了基本的依赖注入情况,我们有时会遇到一些特殊的注入:

  • 跨层级注入:当组件需要从其祖先组件或其他层级的组件中获取依赖项时,我们可以使用@SkipSelf装饰器来解决跨层级注入的问题。
  • 跨组件注入:当组件之间存在嵌套关系,但它们不是直接的父子关系时,我们可以使用@Host装饰器来实现跨组件的依赖注入。
  • 动态注入:有时我们需要在运行时动态地决定要注入的依赖项。在本节中,我们将介绍如何使用Injector类和注入令牌来实现动态注入的功能。
import { Component, SkipSelf, Host, Inject, Injector } from '@angular/core';

// 跨层级注入示例
@Component({
  selector: 'app-parent-component',
  providers: [{ provide: 'sharedValue', useValue: 'Shared Value' }]
})
class ParentComponent {
  constructor(@Inject('sharedValue') private sharedValue: string) {}
}

@Component({
  selector: 'app-child-component',
})
class ChildComponent {
  constructor(@SkipSelf() private sharedValue: string) {}
}

// 跨组件注入示例
@Component({
  selector: 'app-host-component',
  template: `
    <div>
      <app-child-component></app-child-component>
    </div>
  `,
  providers: [{ provide: 'sharedValue', useValue: 'Shared Value' }]
})
class HostComponent {}

@Component({
  selector: 'app-child-component',
})
class ChildComponent {
  constructor(@Host() private sharedValue: string) {}
}

// 动态注入示例
class Logger {
  log(message: string) {
    console.log(message);
  }
}

@Component({
  selector: 'app-dynamic-injection',
  template: `
    <button (click)="logMessage()">Log Message</button>
  `
})
class DynamicInjectionComponent {
  constructor(private injector: Injector) {}

  logMessage() {
    const logger = this.injector.get(Logger);
    logger.log('Hello, dynamic injection!');
  }
}

  1. 跨层级注入:通过使用@Inject装饰器和@SkipSelf装饰器,我们可以从祖先组件中注入依赖项到子组件中。
  2. 跨组件注入:通过使用@Inject装饰器和@Host装饰器,我们可以在嵌套的组件之间实现跨组件的依赖注入。
  3. 动态注入:通过使用Injector类,我们可以在运行时动态地获取依赖项的实例。

3. 使用DI的设计模式

在Angular中,使用设计模式可以更好地组织和管理依赖注入。以下是几个常用的设计模式,可用于实现依赖注入的高度可扩展和可维护的代码

3.1 工厂模式(Factory Pattern)

工厂模式可以用来动态创建对象,封装对象的创建逻辑。在Angular中,我们可以利用工厂模式创建服务或组件的实例。

@Injectable()
class MyServiceFactory {
  createInstance(): MyService {
    // 创建实例的逻辑
    return new MyService();
  }
}

@Component({...})
class MyComponent {
  constructor(private serviceFactory: MyServiceFactory) {
    const myService = serviceFactory.createInstance();
    // 使用myService进行操作
  }
}


在上面的示例中,MyServiceFactory是一个工厂类,负责创建MyService的实例。通过在构造函数中注入MyServiceFactory,我们可以通过工厂类来创建服务的实例,并在组件中使用。

3.2 单例模式(Singleton Pattern)

单例模式用于确保一个类只有一个实例,并提供全局访问点。在Angular中,我们可以通过依赖注入和提供器配置来实现单例模式。

@Injectable({ providedIn: 'root' })
class SingletonService {
  // 单例服务的实现代码
}

@Component({...})
class MyComponent {
  constructor(private singletonService: SingletonService) {
    // 使用singletonService进行操作
  }
}

在上面的示例中,我们使用providedIn: 'root'配置了SingletonService的提供器,这意味着该服务将在根注入器中作为单例提供。这样,无论在应用程序中的哪个组件中使用SingletonService,都将获得同一个实例。

3.3 观察者模式(Observer Pattern)

观察者模式用于实现对象之间的一对多依赖关系,当一个对象状态发生变化时,其他依赖对象将自动得到通知并更新。在Angular中,我们可以使用RxJS库来实现观察者模式。

@Injectable()
class DataService {
  private dataSubject = new Subject<string>();
  data$ = this.dataSubject.asObservable();

  setData(data: string) {
    this.dataSubject.next(data);
  }
}

@Component({...})
class MyComponent {
  constructor(private dataService: DataService) {
    this.dataService.data$.subscribe(data => {
      // 处理数据变化的逻辑
    });
  }
}

在上面的示例中,DataService作为被观察者,通过dataSubject主题来发布数据变化。MyComponent作为观察者,通过订阅data$可观察对象来接收数据变化的通知。

以上仅是几个常见的设计模式在Angular中的应用示例。根据具体的业务需求,我们可以结合不同的设计模式来实现更加灵活和可扩展的代码结构。

3.4 适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)用于将一个类的接口转换成客户端所期望的另一个接口。在Angular中,适配器模式可以用来对接口进行统一,使不兼容的类能够协同工作。下面是一个适配器模式在Angular中的示例:

// 定义外部库的类
class ExternalService {
  request(data: any) {
    // 外部库的请求逻辑
  }
}

// 定义适配器类,将外部库的类适配成Angular服务的接口
@Injectable()
class ExternalServiceAdapter {
  constructor(private externalService: ExternalService) {}

  makeRequest(data: any) {
    this.externalService.request(data);
  }
}

// 在组件中使用适配器
@Component({...})
class MyComponent {
  constructor(private adapter: ExternalServiceAdapter) {
    this.adapter.makeRequest(data);
  }
}

在上面的示例中,ExternalService是一个外部库提供的类,其接口与我们希望在Angular中使用的接口不兼容。为了使其能够在Angular中使用,我们创建了一个适配器类ExternalServiceAdapter,并在构造函数中注入ExternalService的实例。

适配器类中的makeRequest方法充当了适配器的角色,将ExternalService的请求逻辑转换为了适用于Angular的接口。在组件中,我们通过注入适配器类的实例ExternalServiceAdapter,使用makeRequest方法来发起请求。

通过适配器模式,我们可以在Angular中使用不兼容的类,并将其转换为符合我们期望的接口,实现了外部库的适配和集成。这样我们就可以在Angular应用中无缝地使用第三方库或外部服务。

3.5 策略模式(Strategy Pattern)

当策略模式与依赖注入(DI)结合使用时,我们可以在运行时动态选择不同的策略实现,并通过DI将所选的策略注入到相应的组件或服务中。下面是一个结合策略模式和DI的示例:

首先,定义策略接口和具体策略类:

interface DiscountStrategy {
  applyDiscount(price: number): number;
}

class TenPercentDiscountStrategy implements DiscountStrategy {
  applyDiscount(price: number): number {
    return price * 0.9;
  }
}

class TwentyPercentDiscountStrategy implements DiscountStrategy {
  applyDiscount(price: number): number {
    return price * 0.8;
  }
}

然后,在需要使用策略的组件中通过构造函数注入DiscountStrategy

import { Component, Inject } from '@angular/core';

@Component({
  selector: 'app-product',
  template: `{{ finalPrice }}`
})
export class ProductComponent {
  finalPrice: number;

  constructor(@Inject('DISCOUNT_STRATEGY') private discountStrategy: DiscountStrategy) {
    const originalPrice = 100;
    this.finalPrice = this.discountStrategy.applyDiscount(originalPrice);
  }
}

在上述示例中,ProductComponent组件通过构造函数注入了名为DISCOUNT_STRATEGY的依赖项,该依赖项的类型是DiscountStrategy接口。通过使用@Inject装饰器,我们告诉Angular DI系统要注入的是一个策略对象。

最后,我们需要在模块中配置策略的提供器:

import { NgModule } from '@angular/core';
import { ProductComponent } from './product.component';

@NgModule({
  declarations: [ProductComponent],
  providers: [
    { provide: 'DISCOUNT_STRATEGY', useClass: TenPercentDiscountStrategy }
    // 或者:{ provide: 'DISCOUNT_STRATEGY', useClass: TwentyPercentDiscountStrategy }
  ]
})
export class ProductModule {}

原文链接:https://juejin.cn/post/7244487194174767159 作者:咕咕兔兔

(0)
上一篇 2023年6月17日 上午10:42
下一篇 2023年6月17日 上午10:53

相关推荐

发表回复

登录后才能评论