Tôi bị tiêm phụ thuộc, nhưng ai đó có thể giúp tôi hiểu sự cần thiết của một container IoC không?


15

Tôi xin lỗi nếu điều này có vẻ như là một câu hỏi lặp lại, nhưng mỗi lần tôi tìm thấy một bài viết liên quan đến chủ đề này, nó chủ yếu chỉ nói về DI là gì. Vì vậy, tôi nhận được DI, nhưng tôi đang cố gắng tìm hiểu sự cần thiết của một container IoC, thứ mà mọi người dường như đang tham gia. Là điểm của một container IoC thực sự chỉ để "tự động giải quyết" việc triển khai cụ thể các phụ thuộc? Có thể các lớp học của tôi có xu hướng không có một số phụ thuộc và có thể đó là lý do tại sao tôi không thấy vấn đề lớn, nhưng tôi muốn chắc chắn rằng tôi hiểu chính xác tiện ích của container.

Tôi thường phá vỡ logic kinh doanh của mình thành một lớp có thể trông giống như thế này:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

Vì vậy, nó chỉ có một phụ thuộc và trong một số trường hợp, nó có thể có lần thứ hai hoặc thứ ba, nhưng không thường xuyên. Vì vậy, bất cứ điều gì gọi điều này đều có thể vượt qua repo của chính nó hoặc cho phép nó sử dụng repo mặc định.

Theo như sự hiểu biết hiện tại của tôi về một container IoC, tất cả các container thực hiện là giải quyết IDataRep repository. Nhưng nếu đó là tất cả, thì tôi không thấy một tấn giá trị nào trong đó vì các lớp hoạt động của tôi đã xác định dự phòng khi không có sự phụ thuộc nào được đưa vào. Vì vậy, lợi ích duy nhất khác tôi có thể nghĩ đến là nếu tôi có một vài hoạt động như cái này sử dụng cùng một repo dự phòng, tôi có thể thay đổi repo đó ở một nơi là registry / nhà máy / container. Và điều đó thật tuyệt, nhưng phải vậy không?


1
Thường có một phiên bản dự phòng mặc định của sự phụ thuộc không thực sự có ý nghĩa.
Ben Aaronson

Ý bạn thế nào "Dự phòng" là lớp cụ thể được sử dụng gần như mọi lúc trừ các bài kiểm tra đơn vị. Trong thực tế, đây sẽ là cùng một lớp được đăng ký trong container.
Trang web thẩm mỹ

Có, nhưng với vùng chứa: (1) tất cả các đối tượng khác trong vùng chứa có cùng thể hiện ConcreteRepositoryvà (2) bạn có thể cung cấp các phụ thuộc bổ sung cho ConcreteRepository(ví dụ: kết nối cơ sở dữ liệu sẽ phổ biến).
Jules

@ Thẩm mỹ Tôi không có nghĩa là nó luôn luôn là một ý tưởng tồi, nhưng thường thì nó không phù hợp. Ví dụ, nó sẽ ngăn bạn theo kiến ​​trúc củ hành với các tham chiếu dự án của bạn. Ngoài ra có thể không có thực hiện mặc định rõ ràng. Và như Jules nói, các thùng chứa IOC quản lý không chỉ chọn loại phụ thuộc mà còn thực hiện những việc như chia sẻ cá thể và quản lý vòng đời
Ben Aaronson

Tôi sẽ nhận được một chiếc áo thun có nội dung "Thông số chức năng - Tiêm phụ thuộc nguồn gốc!"
Graham

Câu trả lời:


2

Container IoC không phải là về trường hợp bạn có một phụ thuộc. Đó là về trường hợp bạn có 3 phụ thuộc và họ có một số phụ thuộc có phụ thuộc, v.v.

Nó cũng giúp bạn tập trung giải quyết một phụ thuộc và quản lý vòng đời của các phụ thuộc.


10

Có một số lý do tại sao bạn có thể muốn sử dụng bộ chứa IoC.

Dlls không được ước tính

Bạn có thể sử dụng bộ chứa IoC để phân giải một lớp cụ thể từ một dll không được ước tính. Điều này có nghĩa là bạn có thể hoàn toàn phụ thuộc vào sự trừu tượng - tức là giao diện.

Tránh sử dụng new

Một container IoC có nghĩa là bạn hoàn toàn có thể loại bỏ việc sử dụng newtừ khóa để tạo một lớp. Điều này có hai tác dụng. Đầu tiên là nó tách lớp của bạn. Thứ hai (có liên quan) là bạn có thể thả vào các bản giả để thử nghiệm đơn vị. Điều này cực kỳ hữu ích, đặc biệt khi bạn đang tương tác với một quá trình dài.

Viết chống lại trừu tượng

Sử dụng bộ chứa IoC để giải quyết các phụ thuộc cụ thể cho bạn cho phép bạn viết mã chống lại sự trừu tượng, thay vì thực hiện mọi lớp cụ thể mà bạn cần khi bạn cần. Ví dụ, bạn có thể cần mã của mình để đọc dữ liệu từ cơ sở dữ liệu. Thay vì viết lớp tương tác cơ sở dữ liệu, bạn chỉ cần viết một giao diện cho nó và mã theo đó. Bạn có thể sử dụng một bản giả để kiểm tra chức năng của mã bạn đang phát triển khi bạn phát triển nó, thay vì dựa vào việc phát triển lớp tương tác cơ sở dữ liệu cụ thể trước khi bạn có thể kiểm tra mã khác.

Tránh mã giòn

Một lý do khác để sử dụng bộ chứa IoC là bằng cách dựa vào bộ chứa IoC để giải quyết các phụ thuộc của bạn, bạn tránh được việc phải thay đổi mỗi cuộc gọi thành một nhà xây dựng lớp khi bạn thêm hoặc loại bỏ một phụ thuộc. Container IoC sẽ tự động giải quyết các phụ thuộc của bạn. Đây không phải là một vấn đề lớn khi bạn tạo một lớp một lần, nhưng nó là một vấn đề lớn khi bạn tạo lớp ở một trăm nơi.

Quản lý trọn đời và dọn dẹp không quản lý

Lý do cuối cùng tôi sẽ đề cập là việc quản lý vòng đời của đối tượng. Các thùng chứa IoC thường cung cấp khả năng chỉ định tuổi thọ của một đối tượng. Sẽ rất có ý nghĩa khi chỉ định thời gian tồn tại của một đối tượng trong bộ chứa IoC thay vì cố gắng tự quản lý nó theo mã. Quản lý trọn đời bằng tay có thể rất khó khăn. Điều này có thể hữu ích khi làm việc với các đối tượng cần xử lý. Thay vì quản lý việc xử lý các đối tượng của bạn một cách thủ công, một số bộ chứa IoC sẽ quản lý việc xử lý cho bạn, điều này có thể giúp ngăn chặn rò rỉ bộ nhớ và đơn giản hóa cơ sở mã của bạn.

Vấn đề với mã mẫu mà bạn đã cung cấp là lớp bạn đang viết có sự phụ thuộc cụ thể vào lớp ConcreteRep repository. Một container IoC sẽ loại bỏ sự phụ thuộc đó.


22
Đây không phải là lợi thế của container IoC, chúng là lợi thế của việc tiêm phụ thuộc, có thể dễ dàng thực hiện với DI của người nghèo
Ben Aaronson

Viết mã DI tốt mà không có bộ chứa IoC thực sự có thể khá khó khăn. Vâng, có một số sự chồng chéo về các lợi thế, nhưng đây đều là những lợi thế được khai thác tốt nhất với một container IoC.
Stephen

Chà, hai lý do cuối cùng mà bạn thêm vào vì nhận xét của tôi cụ thể hơn về container và cả hai đều là những lý lẽ rất mạnh mẽ theo quan điểm của tôi
Ben Aaronson

"Tránh sử dụng mới" - cũng cản trở việc phân tích mã tĩnh, vì vậy bạn phải bắt đầu sử dụng một cái gì đó như thế này: hmemcpy.github.io/AgentMulder . Các lợi ích khác mà bạn mô tả trong đoạn này là để làm với DI chứ không phải IoC. Ngoài ra các lớp của bạn sẽ vẫn được ghép nối nếu bạn tránh sử dụng mới nhưng sẽ sử dụng các loại cụ thể thay vì giao diện cho các tham số.
Den

1
Nhìn chung, IoC là một thứ không có lỗi, ví dụ như không có đề cập đến nhược điểm lớn: đẩy một lớp lỗi vào thời gian chạy thay vì thời gian biên dịch.
Den

2

Theo nguyên tắc trách nhiệm duy nhất, mỗi lớp chỉ có một trách nhiệm duy nhất. Tạo các thể hiện mới của các lớp chỉ là một trách nhiệm khác, vì vậy bạn phải gói gọn loại mã này trong một hoặc nhiều lớp. Bạn có thể làm điều đó bằng cách sử dụng bất kỳ mẫu sáng tạo nào, ví dụ như nhà máy, nhà xây dựng, container DI, v.v ...

Có những nguyên tắc khác như đảo ngược kiểm soát và đảo ngược phụ thuộc. Trong bối cảnh này, chúng có liên quan đến việc khởi tạo các phụ thuộc. Họ tuyên bố rằng các lớp cấp cao phải được tách rời khỏi các lớp cấp thấp (phụ thuộc) mà họ sử dụng. Chúng ta có thể tách rời mọi thứ bằng cách tạo giao diện. Vì vậy, các lớp cấp thấp phải thực hiện các giao diện cụ thể và các lớp cấp cao phải sử dụng các thể hiện của các lớp thực hiện các giao diện này. (lưu ý: Ràng buộc giao diện thống nhất REST áp dụng cách tiếp cận tương tự ở cấp độ hệ thống.)

Ví dụ, sự kết hợp của các nguyên tắc này (xin lỗi vì mã chất lượng thấp, tôi đã sử dụng một số ngôn ngữ đặc biệt thay vì C #, vì tôi không biết điều đó):

  1. Không có SRP, không có IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. Gần hơn với SRP, không có IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. Có SRP, không có IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. Có SRP, có IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

Vì vậy, cuối cùng chúng tôi đã có một mã trong đó bạn có thể thay thế mọi triển khai cụ thể bằng một mã khác thực hiện cùng một giao diện. Vì vậy, điều này là tốt bởi vì các lớp tham gia được tách rời nhau, họ chỉ biết các giao diện. Một ưu điểm khác là mã khởi tạo có thể tái sử dụng.

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.