【问题标题】:Strategy Pattern with Angular and Typescript使用 Angular 和 Typescript 的策略模式
【发布时间】:2020-11-01 06:14:36
【问题描述】:

我想在 Angular/Typescript 中实现策略模式并在组件中使用服务;服务将策略接口作为构造函数参数。此外,约束是服务依赖于角度级别的其他一些注入服务。

我正在研究文档,但找不到解决方法。我不想以过度设计的代码而告终,寻找简单的解决方案来实现策略模式。

请看下面的模拟代码:

export interface IStrategy {
    calculate(a,b): number;
}

export class MinusStrategy implements IStrategy {
    calculate(a,b): number {
        return a - b;
    }
}

export class PlusStrategy implements IStrategy {
    calculate(a,b): number {
        return a + b;
    }
}

@Injectable() 
export class CalculatorService {
    // I understand it is not possible to have DI of the Interface here for calcStrategy 
    constructor(private http: HttpClient; private calcStrategy: IStrategy);
    
    getByCalc() {
        this.http.get('someurl?calc=' + calcStrategy.calculate(1,1));
    }
}

@Component(// skipped config here...)
export class MyComponent implements OnInit {
    // How to inject appropriate concrete strategy (and either autowire or provide httpClient?)
    constructor(private service: new CalculatorService(httpClient, new MinusStrategy()));
    
    useInComponent() {
        this.service.getByCalc();
    }
}

【问题讨论】:

    标签: angular typescript design-patterns


    【解决方案1】:

    一种方法是定义一个自定义注入令牌并在您的组件提供者声明中使用此令牌(有关更多信息,请参阅https://angular.io/guide/dependency-injection-in-action#supply-a-custom-provider-with-inject):

    export const CalculatorStrategy = new InjectionToken<string>('CalculatorStrategy');
    
    @Component({
        providers: [
            // define the actual strategy-implementation here, setting it as `useClass`-provider
            {provide: CalculatorStrategy, useClass: PlusStrategy}
        ]
    })
    export class MyComponent implements OnInit {
        
        constructor(private service: CalculatorService) {
        }
    
        useInComponent() {
            this.service.getByCalc();
        }
    }
    
    @Injectable()
    export class CalculatorService {
    
        constructor(private http: HttpClient, @Inject(CalculatorStrategy) private calcStrategy: IStrategy);
    
        getByCalc() {
            this.http.get('someurl?calc=' + this.calcStrategy.calculate(1, 1));
        }
    }
    

    【讨论】:

    • 嘿,谢谢,在您的帮助下,我快到了!我得到:InjectionToken MinusStrategy]: NullInjectorError: No provider for InjectionToken MinusStrategy! 如果我在模块级别提供它,例如:{ provide: CalculatorStrategy, useClass: MinusStrategy } 但是我需要为那里的每个具体实现提供一个令牌?我希望我能像你展示的那样动态地交换它?
    • 嗯实际上我很确定这应该可以工作,不幸的是现在无法测试它。与此同时,也许检查一下:stackoverflow.com/a/37002587/3761628 -> 他正在做一些非常相似的事情。
    • 看来我无法让它在服务级别上工作。抛出 NullInjector 错误。我可以在那里提供带有具体实现的注入令牌,但是它失败了,因为我希望客户端代码(组件)决定使用哪种策略。如果你有时间,你也许可以提供 Fiddle / Plunker?谢谢
    • 我明白了——一旦我不再使用移动设备,我就必须检查一下 :) 也许也可以检查 Sang 的解决方案。
    • 当然,我也在检查他的方法。如果可能的话,我更喜欢使用接口。
    【解决方案2】:

    “IStrategy”需要使用抽象类而不是接口。因为 Angular 不支持将接口作为注入令牌。 (https://angular.io/guide/dependency-injection-providers#non-class-dependencies)。 之后,您可以在模块的提供者中定义如下

    { provide: IStrategy, useClass: MinusStrategy }
    

    之后,CalculatorService 将使用 MinusStrategy 将注入该服务的模块中的任何组件。

    export abstract class IStrategy {
        abstract calculate(a,b): number;
    }
    
    export class PlusStrategy extends IStrategy {
        calculate(a,b): number {
            return a + b;
        }
    }
    
    export class MinusStrategy extends IStrategy {
    
        calculate(a,b): number {
            return a - b;
        }
    }
    
    @Injectable({
      providedIn: 'root',
    })
    export class CalculatorService {
    
      constructor(
        private http: HttpClient, 
        private calcStrategy: IStrategy) {};
        
        getByCalc() {
            console.log(`Result is: ${this.calcStrategy.calculate(1,1)}`);
        }
    }
    
    //The module need to add token to providers for Strategy classes.
    @NgModule({
      declarations: [
        ...
      ],
      imports: [
        ...
      ],
      providers: [
        { provide: IStrategy, useClass: MinusStrategy }
      ],
      bootstrap: [...]
    })
    export class AppModule { }
    

    请注意,例如,我为抽象类保留“IStrategy”名称。它应该是“BaseStrategy”或其他内容。

    ================================================ ==============

    [ 2020 年 7 月 12 日 17:00:00 GMT+7 ]

    我创建了一个演示项目并按照这个问题更新了我的方法。你能打开它再看看我的方法吗?

    Stackblitz 链接: https://stackblitz.com/github/sangnt-developer/demo-injection-in-component-level

    Github 链接: https://github.com/sangnt-developer/demo-injection-in-component-level

    【讨论】:

    • 嘿桑,你能提供在@Component 的样子吗?我在您的示例中没有看到它,请参阅上面的代码。 Component 将需要决定使用哪种策略,在您的示例中,您在模块级别硬连线始终使用 MinusStrategy? (一个组件可能使用MinusStrategy,但同一模块中的其他组件可能使用PlusStrategy。谢谢!
    • 我在这里@NenadP。我用代码示例更新了我的答案,打开并再次查看。如果有任何问题,请告诉我:D
    【解决方案3】:

    我的两分钱- 在这种情况下,您不能依赖 DI 提供具体实例。 DI 无法知道每个上下文中需要哪种类型的实例。

    我建议在这里使用工厂模式。比如——

    @Injectable()
    export class StrategyFactory {
      createStrategy<T extends IStrategy>(c: new () => T): T {
        return new c();
      }
    } 
    
    //then you can inject StrategyFactory in your service class, use it like -
    factory.createStrategy(MinusStrategy).calculate(2, 1); 
    

    【讨论】:

    • 有趣的方法。我的意图是提供CalculatorServiceMyComponent。但是你的方法给了我一个想法,就像这样:提供一个由 Angular 管理的工厂,而不是其他人。 @Injectable() export class CalculatorFactory { constructor(private http: HttpClient) {} createCalculator(strategy: IStrategy): Calculator { return new Calculator(this.http, strategy);}} Calculator(现为 CalculatorService)然后在组件中使用:const calculator = this.calculatorFactory.createFactory(new MinusStrategy()); calculator.getByCalc();
    【解决方案4】:

    我认为它可以通过在提供者部分添加服务和令牌来实现组件级别。

     @Component({
        ...
        ...
        proverders: [
        CalculatorService,
        {provide: IStrategy, useClass: 
        PlusStrategy
        ]
        })
    

    然后在构造函数上注入服务

    【讨论】:

      猜你喜欢
      • 2011-04-22
      • 1970-01-01
      • 1970-01-01
      • 2019-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多