ค่อยๆย้าย codebase ไปยังคอนเทนเนอร์ฉีดที่ต้องพึ่งพา


9

ฉันมี codebase ขนาดใหญ่ที่มีซิงเกิล "anti-pattern" จำนวนมากคลาสยูทิลิตี้ที่มีวิธีการคงที่และคลาสที่สร้างการอ้างอิงของตนเองโดยใช้newคำหลัก มันทำให้รหัสยากมากที่จะทดสอบ

ฉันต้องการที่จะโยกย้ายรหัสไปยังภาชนะฉีดพึ่งพา (ในกรณีของฉันมันGuiceเพราะมันเป็นGWTโครงการ) จากความเข้าใจของฉันเกี่ยวกับการฉีดขึ้นอยู่กับมันทั้งหมดหรือเปล่า คลาสทั้งหมดได้รับการจัดการโดย Spring / Guice หรือไม่ เนื่องจาก codebase มีขนาดใหญ่ฉันไม่สามารถแปลงรหัสข้ามคืนได้ ดังนั้นฉันต้องการวิธีที่จะทำมันค่อยๆ

ปัญหาคือเมื่อฉันเริ่มต้นด้วยคลาสที่ต้องถูกฉีดเข้าไปในคลาสอื่นฉันไม่สามารถใช้วิ@Injectในคลาสเหล่านั้นได้เนื่องจากคลาสเหล่านั้นยังไม่ได้รับการจัดการโดยคอนเทนเนอร์ ดังนั้นสิ่งนี้จะสร้างสายโซ่ยาวขึ้นไปถึงคลาส "สุดยอด" ที่ไม่ได้ถูกฉีดเข้าไปที่ใดก็ได้

วิธีเดียวที่ฉันเห็นคือการทำให้Injectorบริบท / แอปพลิเคชันทั่วโลกพร้อมใช้งานผ่านซิงเกิลตันในขณะนั้นเพื่อให้คลาสอื่นสามารถรับถั่วที่ถูกจัดการได้ แต่มันขัดแย้งกับแนวคิดสำคัญที่ไม่เปิดเผยcomposition rootแอปพลิเคชัน

อีกวิธีคือล่างขึ้นบน: เริ่มต้นด้วยคลาส "ระดับสูง" รวมไว้ในคอนเทนเนอร์ฉีดขึ้นต่อกันและค่อย ๆ เลื่อนลงไปที่คลาส "เล็ก" แต่ฉันต้องรอเป็นเวลานานเนื่องจากฉันสามารถทดสอบชั้นเรียนขนาดเล็กที่ยังคงขึ้นอยู่กับกลม / สถิตยศาสตร์

อะไรจะเป็นวิธีที่จะทำให้เกิดการย้ายถิ่นฐานอย่างค่อยเป็นค่อยไป?

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


1
คุณต้องการที่จะตรงจากการมีชั้นเรียนคู่ยากที่จะใช้ภาชนะฉีดพึ่งพา? ก่อนอื่นคุณต้องสร้างการรีแฟคเตอร์ใหม่ให้กับคลาส decouple โดยใช้อินเตอร์เฟสก่อนที่จะคิดถึงการแต่งงานกับกรอบหรือเครื่องมือในการฉีด
Tulains Córdova

ยุติธรรมพอสมควร ดังนั้นฉันจึงมียูทิลิตี้คลาส A ที่ใช้ในคลาสอื่น 30 คลาส (หนึ่งในนั้นคือคลาส B) ทำให้ไม่สามารถทดสอบได้ ฉันคิดว่าจะทำให้คลาส A เป็นตัวสร้างการพึ่งพาของคลาส B ฉันต้องเปลี่ยนการเรียกทั้งหมดเป็นตัวสร้างและผ่าน A ภายใน แต่ฉันจะเอามันมาจากไหน
damluar

หาก B เป็นหน่วยที่เล็กที่สุดที่คุณสามารถนำ DI ไปใช้เพื่อเริ่มต้นฉันไม่เห็นอันตรายใด ๆ ในการสร้าง A ทุกที่ที่คุณสร้าง B ในเวลานั้น - หาก A ไม่มีการพึ่งพา เมื่อลูกบอลกลิ้งไปแล้วคุณสามารถกลับมาใหม่ได้อีกครั้ง
Andy Hunt

@damluar โรงงานที่จะเริ่มต้นด้วย?
Tulains Córdova

1
@damluar คลาสไม่สามารถทดสอบได้เพราะอาศัยคลาสยูทิลิตี้ มันหมายความว่าหน่วยของคุณใหญ่กว่าที่คุณต้องการเล็กน้อย หากคุณสามารถทดสอบคลาสยูทิลิตี้ด้วยตัวเองคุณสามารถใช้คลาสนี้ในการทดสอบสำหรับคลาสอื่น ๆ ทั้งหมด 30 คลาสโดยไม่ต้องกังวล อ่านบล็อก Martin Fowlers เกี่ยวกับการทดสอบหน่วย
gbjbaanb

คำตอบ:


2

ขออภัยC#เป็นภาษาที่ฉันเลือกฉันสามารถอ่านได้Javaแต่อาจจะเขียงไวยากรณ์เพื่อพยายามเขียน ... แนวคิดเดียวกันนี้ใช้ระหว่างC#และJavaแม้ว่าดังนั้นหวังว่าสิ่งนี้จะแสดงขั้นตอนในการที่คุณสามารถย้ายฐานรหัสของคุณให้มากขึ้นได้อย่างไร ทดสอบ

ได้รับ:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

สามารถ refactored ได้อย่างง่ายดายเพื่อใช้ DI โดยไม่ต้องใช้คอนเทนเนอร์ IOC - และคุณอาจทำลายมันลงในหลายขั้นตอน:

(ที่อาจเกิดขึ้น) ขั้นตอนที่หนึ่ง - รับการพึ่งพา แต่ไม่มีการเปลี่ยนแปลงรหัสการโทร (UI):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

Refactor 2 (หรือ refactor แรกหากติดตั้ง IOC container และเปลี่ยนรหัสการโทรทันที):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

ขั้นตอนที่ 2 สามารถทำได้ทางเทคนิคด้วยตัวเอง - แต่จะ (อาจ) ทำงานได้มากขึ้น - ขึ้นอยู่กับว่ามีกี่คลาสที่กำลัง "newing up" ฟังก์ชั่นที่คุณต้องการ DI

พิจารณาไปกับขั้นตอนที่ 1 -> ขั้นตอนที่ 2 เส้นทาง - คุณจะสามารถสร้างการทดสอบหน่วยอิสระของFoo Barในขณะที่ก่อนหน้า refactor ขั้นตอนที่ 1 นั้นไม่สามารถทำได้อย่างง่ายดายโดยไม่ต้องใช้งานจริงของทั้งสองคลาส การทำขั้นตอนที่ 1 -> ขั้นตอนที่ 2 (แทนที่จะเป็นขั้นตอนที่ 2 ทันที) จะทำให้เกิดการเปลี่ยนแปลงเล็กน้อยเมื่อเวลาผ่านไปและคุณจะได้เริ่มต้นชุดควบคุมการทดสอบแล้วเพื่อให้มั่นใจว่า refactor ของคุณทำงานได้ดีขึ้น


0

แนวคิดนี้เหมือนกันไม่ว่าคุณจะใช้ Java, PHP หรือแม้แต่ C # คำถามนี้เกี่ยวกับ Gemma Anible ในวิดีโอ YouTube นี้ค่อนข้างดี:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, ขออภัย!)

คุณแทนที่รหัสที่ไม่สามารถทดสอบได้ด้วย "facade" (เนื่องจากไม่มีคำศัพท์ที่ดีกว่า) ที่เรียกรหัสใหม่ที่ทดสอบได้ จากนั้นคุณสามารถแทนที่การโทรเก่าด้วยบริการฉีด ฉันเคยทำมาแล้วในอดีตและใช้งานได้ดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.