本文将设计模式和Angular 依赖注入DI结合,希望帮助大家更好地理解设计模式在DI中的运用。
1. 介绍DI
依赖注入(Dependency Injection)是现代软件开发中一个重要的概念,它可以帮助我们更好地管理组件之间的依赖关系,并提高代码的可测试性、可维护性和可扩展性。
在软件开发中,组件通常需要依赖其他组件或服务来完成特定的功能。传统的做法是在组件内部直接实例化依赖项,但这样会导致组件与具体的依赖项紧密耦合,难以进行测试和扩展。
依赖注入解决了这个问题,它是一种将依赖项从消费者解耦并委托给外部提供的机制。通过依赖注入,组件不需要关心如何创建或获取依赖项的实例,而是将这个任务交给外部的依赖注入系统来完成。
2. Angular中的DI机制
在Angular中,依赖注入(DI)用于管理组件之间的依赖关系和提供可重用的服务。有几个关键概念:
2.1 注入器(Injector)
注入器是Angular框架中负责实现依赖注入的核心组件。在本节中,我们将深入探讨注入器的工作原理和角色:
- 注入器的层次结构:Angular中的注入器是层次结构的,每个组件都有自己的注入器,这些注入器通过树状结构相互关联。
- 依赖解析过程:当组件需要使用某个依赖项时,注入器会通过递归方式在注入器树中进行依赖项的查找和解析。 详细的层级参考:
www.youtube.com/watch?v=uVG…
- 提供器注册:提供器用于告诉注入器如何创建依赖项的实例。我们将介绍提供器的不同类型(类提供器、值提供器和工厂提供器)以及如何使用提供器来注册依赖项。
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!');
}
}
- 跨层级注入:通过使用
@Inject
装饰器和@SkipSelf
装饰器,我们可以从祖先组件中注入依赖项到子组件中。 - 跨组件注入:通过使用
@Inject
装饰器和@Host
装饰器,我们可以在嵌套的组件之间实现跨组件的依赖注入。 - 动态注入:通过使用
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 {}