วิธีนี้ฉันเขียนโค้ดนี้สามารถทดสอบได้ แต่มีอะไรผิดปกติกับฉันหรือเปล่า


13

IContextฉันมีอินเตอร์เฟซที่เรียกว่า สำหรับวัตถุประสงค์นี้ไม่สำคัญว่าจะทำอะไรยกเว้นดังต่อไปนี้:

T GetService<T>();

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

ในแอปพลิเคชัน ASP.NET MVC ของฉันคอนสตรัคของฉันมีลักษณะเช่นนี้

protected MyControllerBase(IContext ctx)
{
    TheContext = ctx;
    SomeService = ctx.GetService<ISomeService>();
    AnotherService = ctx.GetService<IAnotherService>();
}

ดังนั้นแทนที่จะเพิ่มหลายพารามิเตอร์ในตัวสร้างสำหรับแต่ละบริการ (เพราะจะทำให้รำคาญและใช้เวลานานสำหรับนักพัฒนาที่ขยายแอปพลิเคชัน) ฉันใช้วิธีนี้เพื่อรับบริการ

ตอนนี้ก็รู้สึกผิด แต่วิธีการที่ฉันกำลังเหตุผลมันอยู่ในหัวของฉันนี้ - ฉันจะเยาะเย้ยมัน

ฉันสามารถ. IContextการทดสอบตัวควบคุมนั้นคงไม่ยาก ฉันจะต้องต่อไป:

public class MyMockContext : IContext
{
    public T GetService<T>()
    {
        if (typeof(T) == typeof(ISomeService))
        {
            // return another mock, or concrete etc etc
        }

        // etc etc
    }
}

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


8
นี่เรียกว่าService Locatorและฉันไม่ชอบ มีการเขียนจำนวนมากเกี่ยวกับเรื่องนี้ - ดูmartinfowler.com/articles/inject.htmlและblog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Patternสำหรับผู้เริ่มต้น
Benjamin Hodgson

จากบทความ Martin Fowler: "ฉันมักจะได้ยินคำร้องเรียนว่าผู้ให้บริการประเภทนี้เป็นสิ่งที่ไม่ดีเพราะพวกเขาไม่สามารถทดสอบได้เพราะคุณไม่สามารถทดแทนการใช้งานสำหรับพวกเขาได้แน่นอนคุณสามารถออกแบบได้ไม่ดี เป็นปัญหา แต่คุณไม่ต้องทำในกรณีนี้อินสแตนซ์ของตัวระบุบริการเป็นเพียงตัวยึดข้อมูลอย่างง่ายฉันสามารถสร้างตัวระบุตำแหน่งได้อย่างง่ายดายด้วยการทดสอบการใช้งานบริการของฉัน " คุณช่วยอธิบายได้ไหมว่าทำไมถึงไม่ชอบ อาจจะอยู่ในคำตอบ?
LiverpoolsNumber9

8
เขาพูดถูกการออกแบบไม่ดี public SomeClass(Context c)มันเป็นเรื่องง่าย: รหัสนี้ค่อนข้างชัดเจนใช่ไหม มันฯ , ขึ้นอยู่กับthat SomeClass Contextเอ่อ แต่เดี๋ยวก่อนมันไม่ได้! มันขึ้นอยู่กับการพึ่งพาที่Xได้รับจากบริบทเท่านั้น นั่นหมายความว่าทุกครั้งที่คุณทำการเปลี่ยนแปลงContextมันสามารถทำลายSomeObjectแม้ว่าคุณจะเปลี่ยนแปลงเฉพาะsContext Yแต่ใช่คุณรู้ว่าคุณมีการเปลี่ยนแปลงเพียงYไม่ได้Xดังนั้นSomeClassเป็นเรื่องปกติ แต่การเขียนรหัสที่ดีนั้นไม่เกี่ยวกับสิ่งที่คุณรู้ แต่สิ่งที่พนักงานใหม่รู้เมื่อเขาดูรหัสของคุณเป็นครั้งแรก
valenterry

@DocBrown สำหรับฉันนั่นคือสิ่งที่ฉันพูด - ฉันไม่เห็นความแตกต่างที่นี่ คุณช่วยอธิบายเพิ่มเติมได้ไหม
valenterry

1
@DocBrown ฉันเห็นจุดของคุณแล้ว ใช่ถ้าบริบทของเขาเป็นเพียงการรวมกันของการอ้างอิงทั้งหมดนี่ไม่ใช่การออกแบบที่ไม่ดี มันอาจจะตั้งชื่อไม่ดี แต่นั่นก็เป็นเพียงข้อสันนิษฐาน OP ควรชี้แจงหากมีวิธีการมากขึ้น (วัตถุภายใน) ของบริบท นอกจากนี้การพูดคุยโค้ดนั้นใช้ได้ แต่นี่เป็นโปรแกรมเมอร์เขียนโปรแกรมแลกเปลี่ยนดังนั้นสำหรับฉันเราควรลองดู "เบื้องหลัง" สิ่งต่าง ๆ เพื่อทำให้ OP ดีขึ้น
valenterry

คำตอบ:


5

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

ตัวอย่างแรกของคุณอาจเปลี่ยนเป็น

protected MyControllerBase(IContext ctx)
{
    TheContext = ctx;
    SomeService = ctx.GetSomeService();
    AnotherService = ctx.GetAnotherService();
}

MyControllerBaseไม่คิดว่าจะมีการเปลี่ยนแปลงที่สำคัญของการออกแบบ หากการออกแบบนี้ดีหรือไม่ดีขึ้นอยู่กับข้อเท็จจริงที่คุณต้องการ

  • ตรวจสอบให้แน่ใจTheContext, SomeServiceและAnotherServiceมักจะทั้งหมดเริ่มต้นกับวัตถุจำลองหรือทั้งหมดของพวกเขากับวัตถุจริง
  • หรือเพื่ออนุญาตให้เริ่มต้นพวกเขาด้วยการรวมกันของวัตถุ 3 (ซึ่งหมายความว่าสำหรับกรณีนี้คุณจะต้องผ่านพารามิเตอร์แต่ละรายการ)

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

สิ่งที่เป็นปัญหาคือIContextการเปิดเผยGetServiceวิธีการในที่สาธารณะ IMHO คุณควรหลีกเลี่ยงสิ่งนี้แทนที่จะทำ "วิธีการจากโรงงาน" โดยชัดแจ้ง ดังนั้นมันจะโอเคที่จะใช้GetSomeServiceและGetAnotherServiceวิธีการจากตัวอย่างของฉันโดยใช้ตัวระบุตำแหน่งบริการหรือไม่? IMHO ที่ขึ้นอยู่กับ ตราบใดที่IContextคลาสยังคงสร้างโรงงานนามธรรมที่เรียบง่ายเพื่อวัตถุประสงค์เฉพาะในการจัดทำรายการวัตถุบริการที่ชัดเจนซึ่งเป็นที่ยอมรับของ IMHO บทคัดย่อโรงงานมักจะเป็นเพียงแค่รหัส "กาว" ซึ่งไม่จำเป็นต้องผ่านการทดสอบหน่วย อย่างไรก็ตามคุณควรถามตัวเองว่าในบริบทของวิธีการเช่นGetSomeServiceถ้าคุณต้องการบริการบอกตำแหน่งหรือไม่ว่าการโทรคอนสตรัคเตอร์ที่ชัดเจนจะไม่ง่ายกว่านี้

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


ฉันเห็นด้วยกับสิ่งนี้ ปัญหากับตัวอย่างคือGetServiceวิธีการทั่วไป การเปลี่ยนการตั้งชื่อและวิธีการพิมพ์อย่างชัดเจนจะดีกว่า ยังดีกว่าที่จะสะกดออกอ้างอิงของการIContextใช้งานอย่างชัดเจนในตัวสร้าง
Benjamin Hodgson

1
@BenjaminHodgson: บางครั้งมันอาจจะดีกว่า แต่ก็ไม่ได้ ดีกว่าเสมอ คุณสังเกตได้ว่ากลิ่นของรหัสเมื่อรายการพารามิเตอร์ ctor นานขึ้นเรื่อย ๆ ดูคำตอบเดิมของฉันที่นี่: programmers.stackexchange.com/questions/190120/…
Doc Brown

@DocBrown "กลิ่นรหัส" ของ Constructor over-injection เป็นตัวบ่งชี้ถึงการละเมิด SRP ซึ่งเป็นปัญหาจริง เพียงแค่รวมบริการต่าง ๆ ไว้ในคลาส Facade เพียงเพื่อแสดงให้เห็นว่าเป็นคุณสมบัติไม่ได้ทำอะไรเพื่อแก้ไขปัญหาที่มา ดังนั้น Facade ไม่ควรเป็น wrapper ที่เรียบง่ายรอบ ๆ ส่วนประกอบอื่น ๆ แต่ควรเสนอ API ที่ทำให้เข้าใจง่ายซึ่งจะใช้มัน (หรือทำให้มันง่ายขึ้นด้วยวิธีอื่น) ...
AlexFoxGill

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

คุณอยู่ที่ไหนเมื่อไมโครซอฟท์อบเป็นแบบนี้IValidatableObject?
RubberDuck

15

การออกแบบนี้เรียกว่าService Locator * และฉันไม่ชอบ มีข้อโต้แย้งมากมายกับมัน:

บริการสคู่คุณคอนเทนเนอร์ของคุณ การใช้การฉีดพึ่งพาปกติ (ที่นวกรรมิกสะกดออกมาพึ่งพาอย่างชัดเจน) คุณสามารถแทนที่ภาชนะของคุณด้วยภาชนะบรรจุที่แตกต่างกันได้โดยตรงหรือกลับไปที่new-expressions ด้วยของคุณIContextที่เป็นไปไม่ได้จริงๆ

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

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

บริการสละเมิด Liskov ชดเชยหลักการ เนื่องจากลักษณะการทำงานแตกต่างกันไปตามอาร์กิวเมนต์ชนิดผู้ให้บริการสามารถดูราวกับว่ามันมีจำนวนวิธีการที่ไม่มีที่สิ้นสุดบนอินเตอร์เฟส! เรื่องนี้จะสะกดออกมาในรายละเอียดที่นี่

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

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

* ฉันกำหนด Service Locator เป็นวัตถุด้วยResolve<T>วิธีการทั่วไปที่สามารถแก้ไขการอ้างอิงโดยพลการและใช้ทั่วทั้ง codebase (ไม่ใช่เฉพาะที่ Composition Root) สิ่งนี้ไม่เหมือนกับ Service Facade (วัตถุที่รวมชุดของการอ้างอิงที่รู้จักกันเล็กน้อย) หรือ Abstract Factory (วัตถุที่สร้างอินสแตนซ์ของประเภทเดียว - ประเภทของ Abstract Factory อาจเป็นแบบทั่วไป แต่วิธีการไม่ได้) .


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

@DocBrown ฉันเห็นด้วย ไม่ใช่เพราะมันทำให้ชีวิตของฉันง่ายขึ้น แต่เนื่องจากตัวอย่างส่วนใหญ่ที่ระบุด้านบนไม่เกี่ยวข้องกับรหัสของฉัน
LiverpoolsNumber9

2
สำหรับฉันจุดเด่นของรูปแบบการต่อต้านบริการค้นหาเป็นGetService<T>วิธีการทั่วไป การแก้ไขการพึ่งพาโดยพลการเป็นกลิ่นจริงซึ่งมีอยู่และถูกต้องในตัวอย่างของ OP
Benjamin Hodgson

1
ปัญหาอีกประการหนึ่งของการใช้ Service Locator คือช่วยลดความยืดหยุ่น: คุณสามารถมีการใช้งานเพียงครั้งเดียวของแต่ละอินเตอร์เฟสบริการ หากคุณสร้างสองคลาสที่ต้องพึ่งพา IFrobnicator แต่ต่อมาตัดสินใจว่าควรใช้ DefaultFrobnicator เดิมของคุณ แต่คนอื่น ๆ ควรใช้มัณฑนากร CacheingFrobnicator รอบ ๆ คุณต้องเปลี่ยนรหัสที่มีอยู่ในขณะที่ถ้าคุณ การฉีดการพึ่งพาโดยตรงทั้งหมดที่คุณต้องทำคือเปลี่ยนรหัสการตั้งค่าของคุณ (หรือไฟล์กำหนดค่าหากคุณใช้กรอบงาน DI) ดังนั้นนี่เป็นการละเมิด OCP
จูลส์

1
@DocBrown GetService<T>()วิธีการอนุญาตให้ชั้นเรียนโดยพลการได้รับการร้องขอ: "สิ่งที่วิธีการนี้จะดูที่ DI คอนเทนเนอร์ปัจจุบันของแอพลิเคชันและพยายามที่จะแก้ไขการพึ่งพาฉันคิดว่ามาตรฐานค่อนข้างเป็นธรรม" . ฉันตอบความคิดเห็นของคุณที่ด้านบนสุดของคำตอบนี้ นี่คือบริการค้นหา 100%
AlexFoxGill

5

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

ตกลงเพื่อตอบคำถาม - ขอแก้ไขปัญหาจริงของคุณ:

ดังนั้นแทนที่จะเพิ่มหลายพารามิเตอร์ในตัวสร้างสำหรับแต่ละบริการ (เพราะจะทำให้รำคาญและใช้เวลานานสำหรับนักพัฒนาที่ขยายแอปพลิเคชัน) ฉันใช้วิธีนี้เพื่อรับบริการ

มีคำถามว่าอยู่ในประเด็นนี้เป็นStackOverflow ตามที่ระบุไว้ในหนึ่งในความคิดเห็นที่นั่น:

คำพูดที่ดีที่สุด: "หนึ่งในผลประโยชน์ที่ยอดเยี่ยมของการสร้างคอนสตรัคเตอร์คือการละเมิดหลักการความรับผิดชอบเดี่ยวอย่างชัดเจน"

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

ในทุกโครงการที่ผมได้ทำงานในที่มีการควบคุมฐานมันก็ทำอย่างหมดจดเพื่อวัตถุประสงค์ในการใช้งานร่วมกันคุณสมบัติที่สะดวกสบายและวิธีการเช่นและIsUserLoggedIn() STOP นี่คือการใช้มรดกในทางที่ผิดที่น่ากลัว แต่ให้สร้างส่วนประกอบที่แสดงวิธีการเหล่านี้แทนและพึ่งพาสิ่งที่คุณต้องการ ด้วยวิธีนี้ส่วนประกอบของคุณจะยังคงทดสอบได้และการอ้างอิงของพวกเขาจะชัดเจนGetCurrentUserId()

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

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


+1 - นี่เป็นคำถามใหม่ที่ไม่มีคำตอบอื่นใดได้ตอบจริงๆ
Benjamin Hodgson

1
"หนึ่งในผลประโยชน์ที่ยอดเยี่ยมของการฉีดคอนสตรัคเตอร์ก็คือมันทำให้เกิดการละเมิดหลักการความรับผิดชอบเดี่ยวที่เห็นได้ชัด" รักนั่น คำตอบที่ดีจริงๆ อย่าเห็นด้วยกับมันทั้งหมด กรณีการใช้งานของฉันเป็นเรื่องตลกมากพอไม่ต้องทำซ้ำรหัสในระบบที่จะมีคอนโทรลเลอร์มากกว่า 100 ตัว (อย่างน้อย) อย่างไรก็ตาม SRP ใหม่ - บริการที่ถูกฉีดแต่ละครั้งมีความรับผิดชอบเพียงอย่างเดียวเช่นเดียวกับคอนโทรลเลอร์ที่ใช้พวกเขาทั้งหมด - ผู้หักเหหนึ่งรายจะทำอย่างไร?!
LiverpoolsNumber9

1
@ LiverpoolsNumber9 กุญแจสำคัญคือการย้ายฟังก์ชั่นการใช้งานออกจาก BaseController ไปสู่การพึ่งพาจนกว่าคุณจะไม่มีอะไรเหลืออยู่ใน BaseController ยกเว้นprotectedการมอบหมายหนึ่งซับไปยังส่วนประกอบใหม่ จากนั้นคุณสามารถสูญเสีย BaseController และแทนที่protectedวิธีการเหล่านั้นด้วยการโทรโดยตรงไปยังการพึ่งพา - สิ่งนี้จะทำให้โมเดลของคุณง่ายขึ้นและทำให้การอ้างอิงทั้งหมดของคุณชัดเจน (ซึ่งเป็นสิ่งที่ดีมากในโครงการที่มีคอนโทรลเลอร์ 100s!)
AlexFoxGill

@ LiverpoolsNumber9 - หากคุณสามารถคัดลอกส่วนหนึ่งของ BaseController ของคุณไปยัง Pastebin ได้ฉันสามารถให้คำแนะนำที่เป็นรูปธรรมได้บ้าง
AlexFoxGill

-2

ฉันกำลังเพิ่มคำตอบสำหรับสิ่งนี้ตามการมีส่วนร่วมของคนอื่น ขอบคุณมากเลยทีเดียว ก่อนอื่นนี่คือคำตอบของฉัน: "ไม่ไม่มีอะไรผิดปกติ"

คำตอบ "Service Facade" ของ Doc Brown ฉันยอมรับคำตอบนี้เพราะสิ่งที่ฉันค้นหา (ถ้าคำตอบคือ "ไม่") เป็นตัวอย่างหรือการขยายตัวตามสิ่งที่ฉันทำ เขาให้สิ่งนี้ในการแนะนำว่า A) มันมีชื่อและ B) อาจมีวิธีที่ดีกว่าในการทำเช่นนี้

คำตอบ "ผู้ให้บริการ" ของ Benjamin Hodgson เท่าที่ฉันซาบซึ้งความรู้ที่ฉันได้รับที่นี่สิ่งที่ฉันมีไม่ใช่ "ผู้ให้บริการ" เป็น "Service Facade" ทุกอย่างในคำตอบนี้ถูกต้อง แต่ไม่ใช่สำหรับสถานการณ์ของฉัน

คำตอบของ USR

ฉันจะแก้ไขปัญหานี้ในรายละเอียดเพิ่มเติม:

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

ฉันไม่เสียเครื่องมือและฉันจะไม่สูญเสียการพิมพ์แบบ "คงที่" ซุ้มบริการจะกลับสิ่งที่ฉันได้รับการกำหนดค่าในภาชนะ DI default(T)ฉันหรือ และสิ่งที่ส่งคืนคือ "พิมพ์" การสะท้อนกลับถูกห่อหุ้ม

ฉันไม่เห็นว่าทำไมการเพิ่มบริการเพิ่มเติมเนื่องจากข้อโต้แย้งของตัวสร้างเป็นภาระใหญ่

มันไม่แน่นอน "หายาก" เนื่องจากฉันใช้คอนโทรลเลอร์พื้นฐานทุกครั้งที่ฉันต้องเปลี่ยนคอนสตรัคเตอร์ฉันอาจต้องเปลี่ยนคอนโทรลเลอร์อื่น 10, 100, 1,000 ตัว

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

ฉันกำลังใช้การฉีดพึ่งพา นั่นคือประเด็น

และในที่สุดความเห็นของจูลส์ต่อคำตอบของเบนจามิน ฉันไม่สูญเสียความยืดหยุ่นใด ๆ นี่คือส่วนบริการของฉัน ฉันสามารถเพิ่มพารามิเตอร์ให้มากGetService<T>ที่สุดเท่าที่ฉันต้องการเพื่อแยกความแตกต่างระหว่างการใช้งานที่แตกต่างกันเช่นเดียวกับที่จะทำเมื่อกำหนดค่าคอนเทนเนอร์ DI ตัวอย่างเช่นฉันสามารถเปลี่ยนGetService<T>()เป็นGetService<T>(string extraInfo = null)"ปัญหาที่อาจเกิดขึ้น" ได้


อย่างไรก็ตามขอบคุณทุกคนอีกครั้ง มันมีประโยชน์จริงๆ ไชโย


4
ฉันไม่เห็นด้วยที่คุณมี Service Facade ที่นี่ GetService<T>()วิธีการสามารถที่จะ (พยายามที่จะ) แก้ไขการอ้างอิงโดยพลการ สิ่งนี้ทำให้มันเป็น Service Locator ไม่ใช่ Service Facade อย่างที่ฉันอธิบายไว้ในเชิงอรรถของคำตอบของฉัน หากคุณต้องแทนที่ด้วยชุดGetServiceX()/ GetServiceY()วิธีการขนาดเล็กตามที่ @DocBrown แนะนำดังนั้นมันจะเป็น Facade
Benjamin Hodgson

2
คุณควรอ่านและใส่ใจในส่วนสุดท้ายของบทความนี้เกี่ยวกับAbstract Service Locatorซึ่งเป็นสิ่งที่คุณกำลังทำอยู่ ฉันได้เห็นรูปแบบการต่อต้านนี้เสียหายทั้งโครงการ - ให้ความสนใจเป็นพิเศษกับข้อความ"มันจะทำให้ชีวิตของคุณในฐานะนักพัฒนาการบำรุงรักษาแย่ลงเพราะคุณจะต้องใช้พลังสมองจำนวนมากเพื่อเข้าใจความหมายของการเปลี่ยนแปลงทุกอย่างที่คุณทำ "
AlexFoxGill

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

3
@ LiverpoolsNumber9 ถ้าอย่างนั้นทำไมต้องมีภาษาที่พิมพ์อย่างหนักเลยล่ะ? ข้อผิดพลาดของคอมไพเลอร์เป็นบรรทัดแรกของการป้องกันข้อบกพร่อง การทดสอบหน่วยเป็นครั้งที่สอง คุณจะไม่ถูกทดสอบโดยหน่วยการเรียนรู้เพราะมันเป็นการโต้ตอบระหว่างชั้นเรียนกับการพึ่งพาดังนั้นคุณจะเข้าสู่แนวป้องกันที่สาม: การทดสอบการรวมเข้าด้วยกัน ฉันไม่รู้ว่าคุณรันบ่อยแค่ไหน แต่ตอนนี้คุณกำลังพูดถึงผลตอบรับตามลำดับนาทีหรือมากกว่าแทนที่จะใช้มิลลิวินาทีมันใช้ IDE ของคุณเพื่อขีดเส้นใต้ข้อผิดพลาดคอมไพเลอร์
Ben Aaronson

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