Kế thừa và phụ thuộc tiêm


97

Tôi có một tập hợp các thành phần angle2 sẽ được đưa vào một số dịch vụ. Suy nghĩ đầu tiên của tôi là tốt nhất nên tạo một lớp siêu cấp và tiêm dịch vụ ở đó. Bất kỳ thành phần nào của tôi sau đó sẽ mở rộng lớp cha đó nhưng cách tiếp cận này không hoạt động.

Ví dụ đơn giản:

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
  }
}

Tôi có thể giải quyết điều này bằng cách chèn vào MyServicebên trong mỗi và mọi thành phần và sử dụng đối số đó cho super()cuộc gọi nhưng điều đó rõ ràng là một số loại vô lý.

Làm thế nào để tổ chức các thành phần của tôi một cách chính xác để chúng kế thừa một dịch vụ từ siêu lớp?


Đây không phải là một bản sao. Câu hỏi đang được tham chiếu là về cách xây dựng một lớp DERIVED có thể sử dụng một dịch vụ được đưa vào bởi một siêu lớp đã được xác định. Câu hỏi của tôi là về cách xây dựng một lớp SUPER kế thừa một dịch vụ cho các lớp dẫn xuất. Nó chỉ đơn giản là theo cách khác xung quanh.
maxhb 19/08/2016

Câu trả lời của bạn (nội tuyến trong câu hỏi của bạn) không có ý nghĩa đối với tôi. Bằng cách này, bạn tạo một kim phun độc lập với các ứng dụng Angular của kim phun cho ứng dụng của bạn. Sử dụng new MyService()thay vì tiêm sẽ cho bạn kết quả chính xác (ngoại trừ hiệu quả hơn). Nếu bạn muốn chia sẻ cùng một phiên bản dịch vụ trên các dịch vụ và / hoặc thành phần khác nhau, điều này sẽ không hoạt động. Mỗi lớp sẽ nhận được một MyServicethể hiện khác .
Günter Zöchbauer

Bạn hoàn toàn đúng, mã của tôi sẽ tạo ra rất nhiều trường hợp myService. Tìm thấy một giải pháp mà tránh được điều này nhưng thêm mã khác để các lớp học có nguồn gốc ...
maxhb

Tiêm kim tiêm chỉ là cải tiến khi có một số dịch vụ khác nhau cần phải tiêm ở nhiều nơi. Bạn cũng có thể đưa một dịch vụ có phụ thuộc vào các dịch vụ khác và cung cấp chúng bằng cách sử dụng getter (hoặc phương pháp). Với cách này bạn chỉ cần tiêm một dịch vụ nhưng có thể sử dụng một tập hợp các dịch vụ. Giải pháp của bạn và giải pháp thay thế được đề xuất của tôi đều có nhược điểm là khiến chúng khó thấy lớp nào phụ thuộc vào dịch vụ nào. Tôi muốn tạo ra các công cụ (như mẫu sống trong WebStorm) mà làm cho nó dễ dàng hơn để tạo mã soạn sẵn và được rõ ràng về sự phụ thuộc
Günter Zöchbauer

Câu trả lời:


72

Tôi có thể giải quyết vấn đề này bằng cách chèn MyService bên trong mỗi thành phần và sử dụng đối số đó cho lệnh gọi super () nhưng điều đó rõ ràng là một số loại vô lý.

Nó không vô lý. Đây là cách thức hoạt động của các hàm khởi tạo và chèn hàm tạo.

Mọi lớp có thể tiêm phải khai báo các phụ thuộc là các tham số của phương thức khởi tạo và nếu lớp cha cũng có các thành phần phụ thuộc thì chúng cũng cần được liệt kê trong phương thức khởi tạo của lớp con và được truyền cùng với lớp cha bằng super(dep1, dep2)lời gọi.

Đi vòng quanh một kim phun và nhận được các phụ thuộc một cách cấp bậc có những bất lợi nghiêm trọng.

Nó ẩn các phụ thuộc khiến mã khó đọc hơn.
Nó vi phạm kỳ vọng của một người quen thuộc với cách Angular2 DI hoạt động.
Nó phá vỡ quá trình biên dịch ngoại tuyến tạo ra mã tĩnh để thay thế DI khai báo và bắt buộc để cải thiện hiệu suất và giảm kích thước mã.


4
Chỉ để nói rõ rằng: Tôi cần nó MỌI NƠI. Cố gắng di chuyển sự phụ thuộc đó vào siêu lớp của tôi để MỖI lớp dẫn xuất có thể truy cập dịch vụ mà không cần phải đưa nó riêng lẻ vào từng lớp dẫn xuất.
maxhb 19/08/2016

9
Câu trả lời cho câu hỏi của chính anh ta là một vụ hack xấu xí. Câu hỏi đã chứng minh nó nên được thực hiện như thế nào. Tôi đã giải thích thêm một chút.
Günter Zöchbauer

7
Câu trả lời này là chính xác. OP đã trả lời câu hỏi của chính họ nhưng đã phá vỡ rất nhiều quy ước khi làm như vậy. Việc bạn liệt kê những nhược điểm thực tế cũng rất hữu ích và tôi sẽ bảo đảm cho điều đó - tôi cũng nghĩ như vậy.
dudewad

6
Tôi thực sự muốn (và tiếp tục) sử dụng câu trả lời này để giải quyết "vụ hack" của OP. Nhưng tôi phải nói rằng điều này có vẻ khác xa với DRY và rất khó khăn khi tôi muốn thêm một phụ thuộc vào lớp cơ sở. Tôi vừa phải thêm các mũi tiêm ctor (và các superlệnh gọi tương ứng ) vào khoảng hơn 20 lớp và con số đó sẽ chỉ tăng lên trong tương lai. Vì vậy, hai điều: 1) Tôi không muốn thấy một "cơ sở mã lớn" làm điều này; và 2) Cảm ơn Chúa vì vim qvà vscodectrl+.

5
Chỉ vì nó là bất tiện không có nghĩa là nó là thực hành xấu. Constructors không thuận tiện vì rất khó thực hiện việc khởi tạo đối tượng một cách đáng tin cậy. Tôi cho rằng thực tiễn tồi tệ hơn là xây dựng một dịch vụ cần "một lớp cơ sở tiêm 15 dịch vụ và được kế thừa bởi 6".
Günter Zöchbauer

64

Giải pháp cập nhật, ngăn nhiều trường hợp myService được tạo bằng cách sử dụng bộ phun toàn cầu.

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();
  }
}

Điều này sẽ đảm bảo rằng MyService có thể được sử dụng trong bất kỳ lớp nào mở rộng AbstractComponent mà không cần phải đưa MyService vào mọi lớp dẫn xuất.

Có một số nhược điểm đối với giải pháp này (xem Ccomment từ @ Günter Zöchbauer bên dưới câu hỏi ban đầu của tôi):

  • Tiêm kim tiêm toàn cầu chỉ là cải tiến khi có một số dịch vụ khác nhau cần tiêm ở nhiều nơi. Nếu bạn chỉ có một dịch vụ được chia sẻ thì có lẽ tốt hơn / dễ dàng hơn để đưa dịch vụ đó vào (các) lớp dẫn xuất
  • Giải pháp của tôi và giải pháp thay thế được đề xuất của anh ấy đều có nhược điểm là khiến chúng khó nhận biết lớp nào phụ thuộc vào dịch vụ nào.

Để có lời giải thích bằng văn bản rất tốt về việc tiêm phụ thuộc trong Angular2, hãy xem bài đăng trên blog này đã giúp tôi giải quyết vấn đề rất nhiều: http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html


7
Điều này làm cho nó khá khó để hiểu những dịch vụ thực sự được tiêm.
Simon Dufour

Nó không phải là this.myServiceA = injector.get(MyServiceA);vv?
jenson-button-event

9
Câu trả lời của @Gunter Zochbauer là chính xác. Đây không phải là cách chính xác để làm điều này và phá vỡ nhiều quy ước về góc cạnh. Nó có thể đơn giản hơn trong việc viết mã tất cả các lệnh gọi chèn đó là một "nỗi đau", nhưng nếu bạn muốn hy sinh việc phải viết mã khởi tạo để có thể duy trì một cơ sở mã lớn, thì bạn đang tự bắn vào chân mình. Giải pháp này không thể mở rộng, IMO và sẽ gây ra rất nhiều lỗi khó hiểu.
dudewad

3
Không có nguy cơ xảy ra nhiều trường hợp của cùng một dịch vụ. Bạn chỉ cần cung cấp một dịch vụ ở gốc ứng dụng của mình để ngăn chặn nhiều trường hợp có thể xảy ra trên các nhánh khác nhau của ứng dụng. Chuyển các dịch vụ xuống thay đổi kế thừa không tạo ra các thể hiện mới của các lớp. Câu trả lời của @Gunter Zochbauer là đúng.
ktamlyn

@maxhb bạn đã bao giờ khám phá mở rộng Injectortoàn cầu để tránh phải chuỗi bất kỳ tham số AbstractComponentnào chưa? fwiw, tôi nghĩ rằng việc đưa các thuộc tính phụ thuộc vào một lớp cơ sở được sử dụng rộng rãi để tránh chuỗi hàm tạo lộn xộn là một ngoại lệ hoàn toàn hợp lệ cho quy tắc thông thường.
quentin-starin

4

Thay vì tiêm tất cả các dịch vụ theo cách thủ công, tôi đã tạo một lớp cung cấp các dịch vụ, ví dụ: nó sẽ đưa các dịch vụ vào. Lớp này sau đó được đưa vào các lớp dẫn xuất và được chuyển cho lớp cơ sở.

Lớp có nguồn gốc:

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

Lớp cơ sở:

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

Lớp cung cấp dịch vụ:

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

3
Vấn đề ở đây là bạn có nguy cơ tạo ra một dịch vụ "ngăn kéo rác" về cơ bản chỉ là một proxy cho dịch vụ Injector.
kpup

1

Thay vì tiêm một dịch vụ có tất cả các dịch vụ khác làm phụ thuộc, như sau:

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
    }
}

Tôi sẽ bỏ qua bước bổ sung này và chỉ cần thêm vào tất cả các dịch vụ trong BaseComponent, như sau:

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

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

Kỹ thuật này giả định 2 điều:

  1. Mối quan tâm của bạn hoàn toàn liên quan đến sự kế thừa các thành phần. Rất có thể, lý do bạn đặt câu hỏi này là do có quá nhiều mã không khô (WET?) Mà bạn cần lặp lại trong mỗi lớp dẫn xuất. Nếu bạn muốn hưởng lợi từ một điểm vào duy nhất cho tất cả các thành phần và dịch vụ của mình , bạn sẽ cần thực hiện thêm bước.

  2. Mọi thành phần đều mở rộng BaseComponent

Cũng có một bất lợi nếu bạn quyết định sử dụng hàm tạo của một lớp dẫn xuất, vì bạn sẽ cần gọi super()và chuyển tất cả các phụ thuộc vào. Mặc dù tôi không thực sự thấy trường hợp sử dụng cần thiết phải sử dụng constructorthay thế ngOnInit, nhưng hoàn toàn có khả năng trường hợp sử dụng đó tồn tại.


2
Sau đó, lớp cơ sở có các phụ thuộc vào tất cả các dịch vụ mà bất kỳ lớp con nào của nó cần. ChildComponentA cần ServiceA? Giờ thì ChildComponentB cũng có ServiceA.
knallfrosch

0

Nếu lớp cha được lấy từ trình cắm của bên thứ ba (và bạn không thể thay đổi nguồn), bạn có thể thực hiện việc này:

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

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

hoặc cách tốt nhất (chỉ giữ một tham số trong hàm tạo):

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

Từ những gì tôi hiểu để kế thừa từ lớp cơ sở, trước tiên bạn cần khởi tạo nó. Để khởi tạo nó, bạn cần truyền các tham số bắt buộc của phương thức khởi tạo của nó, do đó bạn truyền chúng từ con sang cha thông qua lời gọi super () sao cho hợp lý. Tất nhiên, máy tiêm là một giải pháp khả thi khác.


0

HACK LỚN

Cách đây một thời gian, một số khách hàng của tôi muốn tham gia hai dự án góc LỚN cho ngày hôm qua (góc v4 thành góc v8). Dự án v4 sử dụng lớp BaseView cho mỗi thành phần và nó chứa tr(key)phương thức cho các bản dịch (trong v8 tôi sử dụng ng-translate). Vì vậy, để tránh chuyển đổi hệ thống dịch và chỉnh sửa hàng trăm mẫu (trong v4) hoặc thiết lập 2 hệ thống dịch song song, tôi sử dụng cách hack xấu xí sau (tôi không tự hào về nó) - trong AppModulelớp tôi thêm hàm tạo sau:

export class AppModule { 
    constructor(private injector: Injector) {
        window['UglyHackInjector'] = this.injector;
    }
}

và bây giờ AbstractComponentbạn có thể sử dụng

export class AbstractComponent {
  private myservice: MyService = null;

  constructor() {
    this.myservice = window['UglyHackInjector'].get(MyService);
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.