Xây dựng tiêm là gì?


47

Tôi đã xem xét các thuật ngữ tiêm xây dựng và tiêm phụ thuộc trong khi xem qua các bài viết về các mẫu thiết kế (Bộ định vị dịch vụ).

Khi tôi loay hoay về việc tiêm constructor, tôi nhận được kết quả không rõ ràng, điều đó khiến tôi phải đăng ký tại đây.

Xây dựng tiêm là gì? Đây có phải là một loại tiêm phụ thuộc cụ thể? Một ví dụ kinh điển sẽ là một trợ giúp tuyệt vời!

Biên tập

Xem lại câu hỏi này sau một khoảng thời gian một tuần, tôi có thể thấy tôi đã lạc lối như thế nào ... Chỉ trong trường hợp có ai khác xuất hiện ở đây, tôi sẽ cập nhật nội dung câu hỏi với một chút học hỏi của tôi. Xin vui lòng bình luận / chính xác. Con Conttor tiêm và thuộc tính tiêm là hai loại tiêm phụ thuộc.


5
Lần truy cập đầu tiên vào google giải thích rõ ràng về nó ... misko.hevery.com/2009/02/19/iêng
user281377

Câu trả lời:


95

Tôi không phải là chuyên gia, nhưng tôi nghĩ tôi có thể giúp. Và vâng, đó là một loại tiêm phụ thuộc cụ thể.

Tuyên bố miễn trừ trách nhiệm: Hầu như tất cả những thứ này đã bị "đánh cắp" từ Ninject Wiki

Hãy xem xét ý tưởng tiêm phụ thuộc bằng cách xem qua một ví dụ đơn giản. Giả sử bạn đang viết trò chơi bom tấn tiếp theo, nơi các chiến binh cao quý chiến đấu vì vinh quang vĩ đại. Đầu tiên, chúng ta sẽ cần một vũ khí phù hợp để trang bị cho các chiến binh của chúng ta.

class Sword 
{
    public void Hit(string target)
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Sau đó, hãy tạo một lớp để đại diện cho chính các chiến binh của chúng ta. Để tấn công kẻ thù của nó, chiến binh sẽ cần một phương thức Attack (). Khi phương thức này được gọi, nó nên sử dụng Kiếm của mình để tấn công đối thủ.

class Samurai
{
    readonly Sword sword;
    public Samurai() 
    {
        this.sword = new Sword();
    }

    public void Attack(string target)
    {
        this.sword.Hit(target);
    }
}

Bây giờ, chúng ta có thể tạo Samurai của mình và chiến đấu!

class Program
{
    public static void Main() 
    {
        var warrior = new Samurai();
        warrior.Attack("the evildoers");
    }
}

Như bạn có thể tưởng tượng, điều này sẽ in Chặt những kẻ bất lương làm sạch một nửa ra bàn điều khiển. Điều này hoạt động tốt, nhưng nếu chúng ta muốn trang bị Samurai của mình bằng vũ khí khác thì sao? Vì Thanh kiếm được tạo ra bên trong nhà xây dựng của lớp Samurai, chúng tôi phải sửa đổi việc triển khai lớp để thực hiện thay đổi này.

Khi một lớp phụ thuộc vào một phụ thuộc cụ thể, nó được cho là được liên kết chặt chẽ với lớp đó . Trong ví dụ này, lớp Samurai được liên kết chặt chẽ với lớp Kiếm. Khi các lớp được liên kết chặt chẽ, chúng không thể thay thế cho nhau mà không làm thay đổi việc thực hiện. Để tránh các lớp khớp nối chặt chẽ, chúng ta có thể sử dụng các giao diện để cung cấp một mức độ gián tiếp. Hãy tạo một giao diện để thể hiện vũ khí trong trò chơi của chúng tôi.

interface IWeapon
{
    void Hit(string target);
}

Sau đó, lớp Sword của chúng tôi có thể thực hiện giao diện này:

class Sword : IWeapon
{
    public void Hit(string target) 
    {
        Console.WriteLine("Chopped {0} clean in half", target);
    }
}

Và chúng ta có thể thay đổi lớp Samurai của mình:

class Samurai
{
    readonly IWeapon weapon;
    public Samurai() 
    {
        this.weapon = new Sword();
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Bây giờ Samurai của chúng tôi có thể được trang bị vũ khí khác nhau. Nhưng chờ đã! Thanh kiếm vẫn được tạo ra bên trong nhà xây dựng của Samurai. Vì chúng tôi vẫn cần thay đổi việc triển khai Samurai để cung cấp cho chiến binh của chúng tôi một vũ khí khác, Samurai vẫn gắn bó chặt chẽ với Kiếm.

May mắn thay, có một giải pháp dễ dàng. Thay vì tạo ra Thanh kiếm từ bên trong nhà xây dựng của Samurai, chúng ta có thể trưng ra nó như một tham số của nhà xây dựng. Còn được gọi là tiêm xây dựng.

class Samurai
{
    readonly IWeapon weapon;
    public Samurai(IWeapon weapon) 
    {
        this.weapon = weapon;
    }
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

Như Giorgio đã chỉ ra, cũng có tiêm tài sản. Đó sẽ là một cái gì đó như:

class Samurai
{
    IWeapon weapon;

    public Samurai() { }


    public void SetWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }

}

Hi vọng điêu nay co ich.


8
Yêu cái này! :) Nó thực sự đọc như một câu chuyện! Chỉ tò mò thôi. Nếu bạn muốn trang bị cùng một Samurai với nhiều vũ khí (tuần tự), tiêm tài sản sẽ là cách tốt nhất phải không?
TheSilverBONS

Vâng, bạn có thể làm điều đó. Trên thực tế, tôi nghĩ rằng bạn sẽ được phục vụ tốt hơn khi chuyển IWeapon làm tham số cho phương thức Tấn công. Giống như "Tấn công void void (IWeapon with, chuỗi đích)". Và để tránh mã trùng lặp, tôi sẽ giữ nguyên phương thức "công khai void Attack (chuỗi đích)" và gọi nó là "this.Attack (this.weapon, target)".
Luiz Angelo

Và sau đó kết hợp với các nhà máy, bạn có thể nhận được các phương thức như CreatSwordSamurai ()! Ví dụ tốt đẹp.
Geerten

2
Ví dụ tuyệt vời! Thật rõ ràng ...
Serge van den Oever

@Luiz Angelo. Xin lỗi, tôi không chắc là tôi đã hiểu nhận xét của bạn: "Trên thực tế, tôi nghĩ rằng bạn sẽ được phục vụ tốt hơn khi chuyển IWeapon như một tham số cho phương thức Tấn công. Giống như ..". Bạn có nghĩa là quá tải phương thức Tấn công trong lớp Samurai?
Pap

3

Tôi sẽ cố gắng nhiều nhất có thể để làm điều này rất cơ bản. Bằng cách đó, bạn có thể xây dựng trên khái niệm và tạo ra các giải pháp hoặc ý tưởng phức tạp của riêng bạn.

Bây giờ hãy tưởng tượng chúng ta có một nhà thờ tên là Jubilee, có các chi nhánh trên toàn thế giới. Mục đích của chúng tôi chỉ đơn giản là nhận được một mục từ mỗi chi nhánh. Đây sẽ là giải pháp với DI;

1) Tạo giao diện IJubilee:

public interface IJubilee
{
    string GetItem(string userInput);
}

2) Tạo một lớp JubileeDI sẽ lấy giao diện IJubilee làm hàm tạo và trả về một mục:

public class JubileeDI
{
    readonly IJubilee jubilee;

    public JubileeDI(IJubilee jubilee)
    {
        this.jubilee = jubilee;
    }

    public string GetItem(string userInput)
    {
        return this.jubilee.GetItem(userInput);
    }
}

3) Bây giờ tạo ra ba phần của Jubilee là JubileeGT, JubileeHOG, JubileeCOV, tất cả đều PHẢI kế thừa giao diện IJubilee. Để giải trí, hãy làm cho một trong số họ thực hiện phương thức của mình dưới dạng ảo:

public class JubileeGT : IJubilee
{
    public virtual string GetItem(string userInput)
    {
        return string.Format("For JubileeGT, you entered {0}", userInput);
    }
}

public class JubileeHOG : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileeHOG, you entered {0}", userInput);
    }
}

public class JubileeCOV : IJubilee
{
    public string GetItem(string userInput)
    {
        return string.Format("For JubileCOV, you entered {0}", userInput);
    }
}

public class JubileeGTBranchA : JubileeGT
{
    public override string GetItem(string userInput)
    {
        return string.Format("For JubileeGT branch A, you entered {0}", userInput);
    }
}

4) Thế thôi! Bây giờ chúng ta hãy xem DI hoạt động:

        JubileeDI jCOV = new JubileeDI(new JubileeCOV());
        JubileeDI jHOG = new JubileeDI(new JubileeHOG());
        JubileeDI jGT = new JubileeDI(new JubileeGT());
        JubileeDI jGTA = new JubileeDI(new JubileeGTBranchA());

        var item = jCOV.GetItem("Give me some money!");
        var item2 = jHOG.GetItem("Give me a check!");
        var item3 = jGT.GetItem("I need to be fed!!!");
        var item4 = jGTA.GetItem("Thank you!");

Đối với mỗi phiên bản của lớp container, chúng tôi chuyển một thể hiện mới của lớp mà chúng tôi yêu cầu, cho phép khớp nối lỏng lẻo.

Hy vọng điều này giải thích khái niệm một cách ngắn gọn.


2

Giả sử rằng bạn có một lớp Amỗi thể hiện cần một thể hiện của lớp khác B.

class A
{
   B b;
}

Nội dung phụ thuộc có nghĩa là tham chiếu Bđược đặt bởi đối tượng quản lý thể hiện của A(trái ngược với việc có lớp Aquản lý tham chiếu Btrực tiếp).

Hàm xây dựng có nghĩa là tham chiếu đến Bđược truyền dưới dạng tham số cho hàm tạo của Avà được đặt trong hàm tạo:

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

Một cách khác là sử dụng phương thức setter (hoặc thuộc tính) để đặt tham chiếu B. Trong trường hợp này, đối tượng quản lý một thể ahiện Atrước tiên phải gọi hàm tạo của Avà sau đó gọi phương thức setter để đặt biến thành viên A.btrước khi ađược sử dụng. Phương thức tiêm sau là cần thiết khi các đối tượng chứa các tham chiếu tuần hoàn với nhau, ví dụ: nếu một thể hiện bcủa Bđiều đó được đặt trong một thể ahiện Achứa tham chiếu ngược tới a.

** BIÊN TẬP**

Dưới đây là một số chi tiết để giải quyết các bình luận.

1. Lớp A quản lý cá thể B

class A
{
    B b;

    A()
    {
        b = new B();
    }
 }

2. Ví dụ B được đặt trong hàm tạo

class A
{
    B b;

    A(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A(b);
}

3. Ví dụ B được đặt bằng phương thức setter

class A
{
    B b;

    A()
    {
    }

    void setB(B b)
    {
        this.b = b;
    }
}

class M
{
    ...
    B b = new B();
    A a = new A();

    a.setB(b);
}

... như trái ngược với có lớp Một quản lý tài liệu tham khảo để B trực tiếp . Điều đó có nghĩa là gì? Làm thế nào bạn sẽ làm điều đó trong mã? (Xin
tha

1
Ví dụ samurai tốt hơn nhiều. Làm ơn hãy ngừng sử dụng các chữ cái cho các biến ...
stuartdotnet

@stuartdotnet: Tôi không tranh cãi về ví dụ samurai trở nên tốt hơn, nhưng tại sao lại ngừng sử dụng các biến một chữ cái? Nếu các biến không có ý nghĩa cụ thể nhưng chỉ là các trình giữ chỗ, các biến một chữ cái nhỏ gọn hơn nhiều và đến điểm. Sử dụng tên dài hơn như itemAhoặc objectAchỉ để làm cho tên dài hơn không phải lúc nào cũng tốt hơn.
Giorgio

1
Sau đó, bạn đã bỏ lỡ quan điểm của tôi - sử dụng tên không mô tả rất khó đọc, theo dõi và hiểu và không phải là cách hay để giải thích một điểm
stuartdotnet

@stuartdotnet: Tôi không nghĩ rằng tôi đã bỏ lỡ quan điểm của bạn: Bạn cho rằng tên mô tả luôn tốt hơn và làm cho mã luôn dễ đọc và tự giải thích hơn.
Giorgio
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.