web-dev-qa-db-ja.com

継承と依存関係の注入

すべてのサービスをインジェクトするangular2コンポーネントのセットがあります。私が最初に考えたのは、スーパークラスを作成してそこにサービスを注入することが最善だろうということでした。私のコンポーネントはいずれもそのスーパークラスを拡張しますが、このアプローチは機能しません。

簡単な例:

export class AbstractComponent {
  constructor(private myservice: MyService) {
    // Inject the service I need for all components
  }
}

export MyComponent extends AbstractComponent {
  constructor(private anotherService: AnotherService) {
    super(); // This gives an error as super constructor needs an argument
  }
}

あらゆるコンポーネントにMyServiceを挿入し、super()呼び出しにその引数を使用することでこれを解決できましたが、それは明らかにある種の不条理です。

スーパークラスからサービスを継承するようにコンポーネントを正しく整理する方法は?

71
maxhb

ソリューションを更新し、グローバルインジェクターを使用してmyServiceの複数のインスタンスが生成されるのを防ぎます。

import {Injector} from '@angular/core';
import {MyServiceA} from './myServiceA';
import {MyServiceB} from './myServiceB';
import {MyServiceC} from './myServiceC';

export class AbstractComponent {
  protected myServiceA:MyServiceA;
  protected myServiceB:MyServiceB;
  protected myServiceC:MyServiceC;

  constructor(injector: Injector) {
    this.settingsServiceA = injector.get(MyServiceA);
    this.settingsServiceB = injector.get(MyServiceB);
    this.settingsServiceB = injector.get(MyServiceC);
  }
}

export MyComponent extends AbstractComponent {
  constructor(
    private anotherService: AnotherService,
    injector: Injector
  ) {
    super(injector);

    this.myServiceA.JustCallSomeMethod();
    this.myServiceB.JustCallAnotherMethod();
    this.myServiceC.JustOneMoreMethod();
  }
}

これにより、すべての派生クラスにMyServiceを注入することなく、AbstractComponentを拡張するクラス内でMyServiceを使用できるようになります。

このソリューションにはいくつかの短所があります(元の質問の下にある@GünterZöchbauerのコメントを参照してください)。

  • グローバルインジェクターの注入は、多くの場所で注入する必要があるいくつかの異なるサービスがある場合にのみ改善されます。共有サービスが1つしかない場合は、おそらく派生したクラス内にそのサービスを注入する方が良い/簡単です
  • 私のソリューションと彼が提案した代替案には、どちらのクラスがどのサービスに依存しているかを確認するのが難しくなるという欠点があります。

Angular2での依存性注入の非常によく書かれた説明については、このブログ投稿を参照してください。問題を解決するのに大いに役立ちました。 http://blog.thoughtram.io/angular/2015/05/18/dependency-injection- in-angular-2.html

52
maxhb

私はこれをすべてのコンポーネント内にMyServiceを注入し、super()呼び出しにその引数を使用することで解決できましたが、それは明らかにある種の馬鹿げたものです。

それは不条理ではありません。これが、コンストラクターとコンストラクターインジェクションの仕組みです。

すべての注入可能クラスは、コンストラクターのパラメーターとして依存関係を宣言する必要があり、スーパークラスにも依存関係がある場合、サブクラスのコンストラクターにもリストし、super(dep1, dep2)呼び出しでスーパークラスに渡す必要があります。

インジェクターを迂回して依存関係を取得することは、重大な欠点があります。

依存関係を隠し、コードを読みにくくします。
Angular2 DIの仕組みに精通している人の期待に反します。
静的なコードを生成するオフラインコンパイルを中断して、宣言型および命令型DIを置き換え、パフォーマンスを向上させ、コードサイズを削減します。

57

すべてのサービスを手動で注入する代わりに、サービスを提供するクラスを作成しました。たとえば、サービスを注入します。次に、このクラスは派生クラスに注入され、基本クラスに渡されます。

派生クラス:

@Component({
    ...
    providers: [ProviderService]
})
export class DerivedComponent extends BaseComponent {
    constructor(protected providerService: ProviderService) {
        super(providerService);
    }
}

基本クラス:

export class BaseComponent {
    constructor(protected providerService: ProviderService) {
        // do something with providerService
    }
}

サービス提供クラス:

@Injectable()
export class ProviderService {
    constructor(private _apiService: ApiService, private _authService: AuthService) {
    }
}
3
Leukipp

以下のように、依存関係として他のすべてのサービスを持つサービスを注入する代わりに:

class ProviderService {
    constructor(private service1: Service1, private service2: Service2) {}
}

class BaseComponent {
    constructor(protected providerService: ProviderService) {}

    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        // Access to all application services with providerService
        this.providerService.service1
    }
}

この追加手順をスキップして、次のようにBaseComponentにすべてのサービスを挿入するだけです。

class BaseComponent {
    constructor(protected service1: Service1, protected service2: Service2) {}
}

class DerivedComponent extends BaseComponent {
    ngOnInit() {
        this.service1;
        this.service2;
    }
}

この手法は2つのことを前提としています。

  1. あなたの懸念は、コンポーネントの継承に完全に関連しています。ほとんどの場合、この質問にたどり着いた理由は、各派生クラスで繰り返す必要がある非ドライ(WET?)コードが圧倒的に多いためです。すべてのコンポーネントおよびサービスに対して単一のエントリポイントを利用したい場合は、追加の手順を実行する必要があります。

  2. すべてのコンポーネントはBaseComponentを拡張します

派生クラスのコンストラクターを使用することを決定した場合にも、super()を呼び出してすべての依存関係を渡す必要があるため、欠点があります。 constructorの代わりにngOnInitの使用を必要とするユースケースは実際にはありませんが、そのようなユースケースが存在する可能性は完全にあります。

1
maximedupre

親クラスがサードパーティのプラグインから取得されている場合(そしてソースを変更できない場合)、これを行うことができます:

import { Injector } from '@angular/core';

export MyComponent extends AbstractComponent {
  constructor(
    protected injector: Injector,
    private anotherService: AnotherService
  ) {
    super(injector.get(MyService));
  }
}

または最も良い方法(コンストラクターにパラメーターを1つだけ保持):

import { Injector } from '@angular/core';

export MyComponent extends AbstractComponent {
  private anotherService: AnotherService;

  constructor(
    protected injector: Injector
  ) {
    super(injector.get(MyService));
    this.anotherService = injector.get(AnotherService);
  }
}
0
dlnsk

基本クラスから継承するために私が理解していることから、最初にインスタンス化する必要があります。インスタンス化するには、コンストラクタに必要なパラメータを渡す必要があります。したがって、子から親にsuper()呼び出しを介して渡すので、意味があります。もちろん、インジェクターも実行可能なソリューションです。

0
Ihor Bodnarchuk