วิธีการหลีกเลี่ยงการพึ่งพาการสร้างบ้าฉีด?


300

ฉันพบว่าตัวสร้างของฉันเริ่มมีลักษณะเช่นนี้:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

ด้วยรายการพารามิเตอร์ที่เพิ่มขึ้น เนื่องจาก "คอนเทนเนอร์" เป็นคอนเทนเนอร์ฉีดที่ขึ้นต่อกันของฉันทำไมฉันจึงทำสิ่งนี้ไม่ได้:

public MyClass(Container con)

สำหรับทุกชั้น? ข้อเสียคืออะไร? ถ้าฉันทำเช่นนี้มันให้ความรู้สึกเหมือนกำลังใช้ไฟฟ้าสถิตย์ กรุณาแบ่งปันความคิดของคุณเกี่ยวกับ IoC และบ้าคลั่งฉีดพึ่งพา


64
ทำไมคุณผ่านภาชนะบรรจุ ฉันคิดว่าคุณอาจเข้าใจผิด IOC
— Paul Creasey

33
หากคอนสตรัคเตอร์ของคุณต้องการพารามิเตอร์มากขึ้นหรือมากขึ้นคุณอาจทำมากเกินไปในคลาสเหล่านั้น
— Austin Salonen

38
นั่นไม่ใช่วิธีที่คุณใช้ในการสร้างคอนสตรัคชัน วัตถุไม่รู้เกี่ยวกับคอนเทนเนอร์ IoC เลยและไม่ควรใช้
— duffymo

คุณสามารถสร้าง constructor ที่ว่างเปล่าซึ่งคุณเรียก DI โดยตรงเพื่อขอสิ่งที่คุณต้องการ ที่จะลบตัวสร้าง Madnes แต่คุณต้องแน่ใจว่าคุณกำลังใช้ DI Interface .. ในกรณีที่คุณเปลี่ยนระบบ DI ลงครึ่งหนึ่ง สุจริต .. ไม่มีใครจะกลับมาทำแบบนี้แม้ว่านี่จะเป็นสิ่งที่ DI จะแทรกเข้าไปในคอนสตรัคเตอร์ของคุณ doh
— Piotr Kula

คำตอบ:


409

คุณถูกต้องว่าถ้าคุณใช้คอนเทนเนอร์เป็นตัวระบุบริการมันเป็นโรงงานสแตติกที่ได้รับเกียรติมากหรือน้อย ด้วยเหตุผลหลายประการฉันคิดว่านี่เป็นรูปแบบการต่อต้านผมคิดว่านี้ต่อต้านรูปแบบ

หนึ่งในผลประโยชน์ที่ยอดเยี่ยมของ Constructor Injection คือการละเมิดหลักการความรับผิดชอบแบบเดี่ยวอย่างเห็นได้ชัด

ที่เกิดขึ้นเมื่อมันถึงเวลาที่จะrefactor บริการซุ้ม กล่าวโดยสรุปให้สร้างอินเทอร์เฟซใหม่ที่มีความหยาบมากขึ้นซึ่งซ่อนการโต้ตอบระหว่างการพึ่งพาแบบละเอียดบางส่วนหรือทั้งหมดที่คุณต้องการในปัจจุบัน


8
+1 สำหรับการนับจำนวนความพยายามในการปรับโครงสร้างให้เป็นแนวคิดเดียว น่ากลัว :)
— Ryan Emerle

46
จริงเหรอ คุณเพิ่งสร้างทางอ้อมเพื่อย้ายพารามิเตอร์เหล่านั้นไปยังคลาสอื่น แต่ยังคงมีอยู่! ซับซ้อนมากขึ้นเพื่อจัดการกับพวกเขา
— ปฏิเสธไม่ได้

23
@ โต้แย้งได้: ในกรณีที่ความเสื่อมโทรมที่เราย้ายการพึ่งพาทั้งหมดไปสู่การรวมบริการฉันยอมรับว่ามันเป็นอีกระดับหนึ่งของการอ้อมที่ไม่มีประโยชน์ดังนั้นการเลือกคำพูดของฉันจึงเล็กน้อย อย่างไรก็ตามประเด็นก็คือเราย้ายการอ้างอิงบางส่วนที่ละเอียดอ่อนไปยัง Aggregate Service สิ่งนี้ จำกัด จำนวนการเปลี่ยนลำดับการพึ่งพาทั้งในบริการรวมใหม่และสำหรับการพึ่งพาที่เหลืออยู่ ทำให้ทั้งคู่จัดการได้ง่ายขึ้น
— Mark Seemann

92
คำพูดที่ดีที่สุด: "หนึ่งในผลประโยชน์ที่ยอดเยี่ยมของ Constructor Injection คือการละเมิดหลักการความรับผิดชอบแบบเดี่ยวอย่างเห็นได้ชัด"
— Igor Popov

2
@ DonBox ในกรณีนั้นคุณสามารถเขียนการประยุกต์ใช้วัตถุเป็นโมฆะเพื่อหยุดการเรียกซ้ำ ไม่ใช่สิ่งที่คุณต้องการ แต่ประเด็นคือ Constructor Injection ไม่ได้ป้องกันวงจร - มันทำให้ชัดเจนว่าพวกมันอยู่ที่นั่น
— Mark Seemann

66

ฉันไม่คิดว่าผู้สร้างคลาสของคุณควรมีการอ้างอิงถึงช่วงเวลาคอนเทนเนอร์ IOC ของคุณ สิ่งนี้แสดงถึงการพึ่งพาที่ไม่จำเป็นระหว่างคลาสของคุณและคอนเทนเนอร์ (ชนิดของ IOC อ้างอิงที่พยายามหลีกเลี่ยง!)


+1 ขึ้นอยู่กับคอนเทนเนอร์ IoC ทำให้มันยากที่จะเปลี่ยนคอนเทนเนอร์นั้นในภายหลังโดยไม่ต้องเปลี่ยนพวงของรหัสในคลาสอื่น ๆ ทั้งหมด
— Tseng

1
คุณจะนำ IOC ไปใช้อย่างไรโดยไม่ต้องมีพารามิเตอร์ของอินเตอร์เฟสบนตัวสร้าง ฉันอ่านโพสต์ของคุณผิดหรือเปล่า?
— J Hunt

@J Hunt ฉันไม่เข้าใจความคิดเห็นของคุณ สำหรับฉันพารามิเตอร์ของอินเทอร์เฟซหมายถึงพารามิเตอร์ที่เป็นอินเทอร์เฟซสำหรับการอ้างอิงเช่นถ้าคอนเทนเนอร์การฉีดของคุณเริ่มต้นMyClass myClass = new MyClass(IDependency1 interface1, IDependency2 interface2)(พารามิเตอร์อินเตอร์เฟส) สิ่งนี้ไม่เกี่ยวข้องกับการโพสต์ของ @ derivation ซึ่งฉันตีความว่าการพูดว่าถังบรรจุที่ต้องพึ่งพาไม่ควรฉีดตัวเองเข้าไปในวัตถุเช่นMyClass myClass = new MyClass(this)
— John Doe

25

ความยากลำบากในการส่งผ่านพารามิเตอร์ไม่ใช่ปัญหา ปัญหาคือว่าห้องเรียนของคุณทำมากเกินไปและควรแยกย่อยมากขึ้น

การฉีดพึ่งพาสามารถทำหน้าที่เป็นคำเตือนล่วงหน้าสำหรับชั้นเรียนที่มีขนาดใหญ่เกินไปโดยเฉพาะเนื่องจากความเจ็บปวดที่เพิ่มขึ้นของการส่งผ่านในการพึ่งพาทั้งหมด


44
แก้ไขให้ถูกต้องหากฉันผิด แต่ในบางจุดคุณต้อง 'กาวทุกอย่างเข้าด้วยกัน' และทำให้คุณต้องได้รับมากกว่าสองสามอย่างสำหรับสิ่งนั้น ตัวอย่างเช่นในเลเยอร์มุมมองเมื่อสร้างเทมเพลตและข้อมูลสำหรับพวกเขาคุณต้องคว้าข้อมูลทั้งหมดจากการพึ่งพาต่างๆ (เช่น 'บริการ') จากนั้นนำข้อมูลทั้งหมดนี้ไปไว้ในเทมเพลตและหน้าจอ หากหน้าเว็บของฉันมี 10 'บล็อก' ข้อมูลที่แตกต่างกันดังนั้นฉันต้องการชั้นเรียนที่แตกต่างกัน 10 ชั้นเพื่อให้ข้อมูลแก่ฉัน ดังนั้นฉันต้องการการอ้างอิง 10 รายการในคลาส View / Template
— แอนดรู

4

ฉันเจอคำถามที่คล้ายกันเกี่ยวกับการฉีดตามผู้สร้างและความซับซ้อนที่จะผ่านในการพึ่งพาทั้งหมด

หนึ่งในวิธีการที่ฉันเคยใช้ในอดีตคือการใช้รูปแบบซุ้มของแอปพลิเคชันโดยใช้ชั้นบริการ นี่จะเป็น API แบบคร่าวๆ ถ้าบริการนี้ขึ้นอยู่กับที่เก็บมันจะใช้ setter injection ของคุณสมบัติส่วนตัว สิ่งนี้ต้องสร้างโรงงานที่เป็นนามธรรมและย้ายตรรกะของการสร้างที่เก็บลงในโรงงาน

รหัสรายละเอียดพร้อมคำอธิบายอยู่ที่นี่

แนวทางปฏิบัติที่ดีที่สุดสำหรับ IoC ในชั้นบริการที่ซับซ้อน


3

ฉันอ่านกระทู้ทั้งหมดนี้สองครั้งและฉันคิดว่าผู้คนตอบสนองโดยสิ่งที่พวกเขารู้ไม่ใช่สิ่งที่ถูกถาม

คำถามดั้งเดิมของ JP ดูเหมือนว่าเขาจะสร้างวัตถุโดยการส่งตัวแก้ปัญหาแล้วเรียนเป็นกลุ่ม แต่เราสมมติว่าคลาส / วัตถุเหล่านั้นเป็นบริการของตัวเอง เกิดอะไรขึ้นถ้าพวกเขาไม่ได้?

JP หากคุณกำลังมองหาที่จะใช้ประโยชน์จาก DI และต้องการได้รับเกียรติจากการผสมการฉีดเข้ากับข้อมูลเชิงบริบทไม่มีรูปแบบเหล่านี้ (หรือที่เรียกว่า "รูปแบบการต่อต้าน") กล่าวโดยเฉพาะ อันที่จริงแล้วการใช้แพ็คเกจซึ่งจะช่วยคุณในความพยายามดังกล่าว

Container.GetSevice<MyClass>(someObject1, someObject2)

... รูปแบบนี้ไม่ค่อยรองรับ ฉันเชื่อว่าความยากลำบากในการเขียนโปรแกรมการสนับสนุนดังกล่าวเพิ่มให้กับประสิทธิภาพที่น่าสังเวชซึ่งจะเกี่ยวข้องกับการใช้งานทำให้ไม่น่าสนใจสำหรับนักพัฒนาโอเพนซอร์ซ

แต่ควรทำเพราะฉันควรจะสามารถสร้างและลงทะเบียนโรงงานสำหรับ MyClass'es และโรงงานนั้นควรจะสามารถรับข้อมูล / อินพุตที่ไม่ได้ถูกผลักดันให้กลายเป็น "บริการ" เพียงเพื่อประโยชน์ในการผ่าน ข้อมูล. หาก "รูปแบบการต่อต้าน" เป็นเรื่องเกี่ยวกับผลกระทบเชิงลบดังนั้นการบังคับให้มีบริการประเภทเทียมสำหรับการส่งผ่านข้อมูล / รุ่นนั้นเป็นผลลบอย่างแน่นอน

มีกรอบที่อาจช่วยแม้ว่าพวกเขาจะดูน่าเกลียดเล็กน้อย ตัวอย่างเช่น Ninject:

การสร้างอินสแตนซ์โดยใช้ Ninject พร้อมพารามิเตอร์เพิ่มเติมในตัวสร้าง

สำหรับ NET. นั้นเป็นที่นิยมและยังไม่มีที่ไหนที่สะอาดเท่าที่ควร แต่ฉันแน่ใจว่ามีบางอย่างในภาษาที่คุณเลือกใช้


3

การฉีดคอนเทนเนอร์เป็นทางลัดที่คุณจะต้องเสียใจในที่สุด

การฉีดไม่ได้เป็นปัญหามักจะเป็นอาการของข้อบกพร่องของโครงสร้างอื่น ๆ โดยเฉพาะอย่างยิ่งการแยกความกังวล นี่ไม่ใช่ปัญหาเดียว แต่มีหลายแหล่งและสิ่งที่ทำให้สิ่งนี้ยากในการแก้ไขคือคุณจะต้องจัดการกับสิ่งเหล่านั้นทั้งหมดในบางครั้งในเวลาเดียวกัน

นี่คือรายการที่ไม่สมบูรณ์ของสิ่งที่ต้องระวัง

การออกแบบโดเมนไม่ดี (รวมรากของ…. ฯลฯ )

การแยกข้อกังวลไม่ดี (องค์ประกอบบริการคำสั่งคิวรี) ดู CQRS และการจัดหากิจกรรม

หรือผู้ทำแผนที่ (ระวังสิ่งเหล่านี้สามารถนำคุณไปสู่ปัญหา)

ดูแบบจำลองและ DTO อื่น ๆ (อย่านำมาใช้ซ้ำและพยายามทำให้น้อยที่สุด !!!!)


2

ปัญหา:

1) คอนสตรัคเตอร์ที่มีรายการพารามิเตอร์เพิ่มขึ้นเรื่อย ๆ

2) ถ้าคลาสนั้นได้รับการสืบทอด (Ex:) RepositoryBaseดังนั้นการเปลี่ยนลายเซ็นคอนสตรัคเตอร์ทำให้เกิดการเปลี่ยนแปลงในคลาสที่ได้รับ

โซลูชันที่ 1

ผ่านIoC Containerไปยังตัวสร้าง

ทำไม

  • ไม่มีการเพิ่มรายการพารามิเตอร์อีกต่อไป
  • ลายเซ็นของตัวสร้างกลายเป็นเรื่องง่าย

ทำไมจะไม่ล่ะ

  • ทำให้คุณเรียนควบคู่กับคอนเทนเนอร์ IoC อย่างแน่นหนา (นั่นทำให้เกิดปัญหาเมื่อ 1. คุณต้องการใช้คลาสนั้นในโครงการอื่นที่คุณใช้คอนเทนเนอร์ IoC อื่น 2. คุณตัดสินใจเปลี่ยนคอนเทนเนอร์ IoC)
  • ทำให้ชั้นเรียนมีความหมายน้อยลง (คุณไม่สามารถดูตัวสร้างคลาสและพูดสิ่งที่มันต้องการสำหรับการทำงานได้จริง ๆ )
  • ชั้นสามารถเข้าถึงบริการทั้งหมดที่อาจเกิดขึ้น

โซลูชันที่ 2

สร้างคลาสที่จัดกลุ่มบริการทั้งหมดและส่งต่อไปยังตัวสร้าง

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

คลาสที่ได้รับมา

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

ทำไม

  • การเพิ่มการพึ่งพาใหม่ให้กับคลาสไม่ส่งผลต่อคลาสที่ได้รับ
  • คลาสเป็นผู้ไม่เชื่อเรื่องพระเจ้าของคอนเทนเนอร์ IoC
  • คลาสเป็นคำอธิบาย (ในแง่มุมของการอ้างอิง) โดยการประชุมหากคุณต้องการที่จะรู้ว่าAขึ้นอยู่กับชั้นเรียนที่มีการสะสมข้อมูลA.Dependency
  • ลายเซ็นตัวสร้างกลายเป็นเรื่องง่าย

ทำไมจะไม่ล่ะ

  • จำเป็นต้องสร้างคลาสเพิ่มเติม
  • การลงทะเบียนบริการมีความซับซ้อน(คุณต้องลงทะเบียนX.Dependencyแยกต่างหากทุกครั้ง)
  • แนวคิดเหมือนกับผ่าน IoC Container
  • ..

โซลูชันที่ 2 เป็นเพียงดิบ แต่ถ้ามีข้อโต้แย้งที่มั่นคงกับมันความคิดเห็นเชิงอธิบายจะได้รับการชื่นชม


1

นี่คือวิธีที่ฉันใช้

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

นี่คือวิธีการที่หยาบวิธีดำเนินการฉีดและเรียกใช้ตัวสร้างหลังจากฉีดค่า เป็นโปรแกรมที่ทำงานได้อย่างสมบูรณ์

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

ฉันกำลังทำงานเกี่ยวกับโครงการงานอดิเรกซึ่งทำงานเช่นนี้ https://github.com/Jokine/ToolProject/tree/Core


-8

คุณใช้กรอบการฉีดแบบพึ่งพาอะไร คุณเคยลองใช้การฉีด setter แทนหรือไม่?

ประโยชน์สำหรับการสร้างตามการฉีดคือมันดูเป็นธรรมชาติสำหรับโปรแกรมเมอร์ Java ที่ไม่ได้ใช้กรอบ DI คุณต้องการ 5 สิ่งในการเริ่มต้นคลาสจากนั้นคุณมี 5 อาร์กิวเมนต์สำหรับตัวสร้างของคุณ ข้อเสียคือสิ่งที่คุณสังเกตเห็นมันจะไม่สะดวกเมื่อคุณมีการพึ่งพามากมาย

ด้วย Spring คุณสามารถส่งผ่านค่าที่ต้องการกับ setters แทนและคุณสามารถใช้ @required คำอธิบายประกอบเพื่อบังคับให้พวกเขาถูกฉีด ข้อเสียคือคุณต้องย้ายรหัสการเริ่มต้นจากตัวสร้างไปยังวิธีอื่นและมีการเรียก Spring ว่าหลังจากการอ้างอิงทั้งหมดถูกฉีดโดยทำเครื่องหมายด้วย @PostConstruct ฉันไม่แน่ใจเกี่ยวกับกรอบอื่น ๆ แต่ฉันคิดว่าพวกเขาทำสิ่งที่คล้ายกัน

ทั้งสองวิธีทำงานมันเป็นเรื่องของการตั้งค่า


21
เหตุผลในการฉีดคอนสตรัคเตอร์คือการทำให้การอ้างอิงชัดเจนขึ้นไม่ใช่เพราะมันดูเป็นธรรมชาติมากขึ้นสำหรับนักพัฒนาจาวา
— L-Four

8
ความคิดเห็นปลาย แต่คำตอบนี้ทำให้ฉันหัวเราะ :)
— เฟรดเดอ Prijck

1
+1 สำหรับการฉีดตาม setter ถ้าฉันมีบริการและที่เก็บข้อมูลที่กำหนดไว้ในชั้นเรียนของฉันมันค่อนข้างชัดเจนว่าพวกเขาเป็นผู้อ้างอิง .. ฉันไม่จำเป็นต้องเขียนคอนสตรัคเตอร์ VB6 ขนาดใหญ่ที่มองหาและทำการกำหนดรหัสโง่ ๆ ในคอนสตรัคเตอร์ มันค่อนข้างชัดเจนว่าการพึ่งพาอาศัยกันบนฟิลด์ที่ต้องการคืออะไร
— Piotr Kula

ตาม 2018 Spring แนะนำอย่างเป็นทางการว่าไม่ควรใช้ setter injection ยกเว้นการพึ่งพาที่มีค่าเริ่มต้นที่สมเหตุสมผล เช่นเดียวกับในกรณีที่การขึ้นต่อกันนั้นจำเป็นต้องมีในชั้นเรียนแนะนำให้ทำการสร้างการฉีด ดูการสนทนาเกี่ยวกับ setter vs ctor DI
— John Doe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.