ฉันคิดว่าวิธีที่ง่ายที่สุดในการทำความเข้าใจความแตกต่างระหว่างทั้งสองและทำไม DI container จึงดีกว่า Service locator มากคือการคิดว่าทำไมเราถึงต้องพึ่งการผกผันในตอนแรก
เราทำการผกผันของการพึ่งพาเพื่อให้แต่ละคลาสระบุอย่างชัดเจนถึงสิ่งที่มันขึ้นอยู่กับการดำเนินการ เราทำเช่นนี้เพราะสิ่งนี้สร้างการมีเพศสัมพันธ์อย่างหลวม ๆ ที่เราสามารถทำได้ สิ่งที่ง่ายกว่าคือการทดสอบและรีแฟคเตอร์ (และโดยทั่วไปต้องการการรีแฟคเตอร์น้อยที่สุดในอนาคตเพราะรหัสนั้นสะอาดกว่า)
ลองดูที่คลาสต่อไปนี้:
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
ในคลาสนี้เราจะระบุอย่างชัดเจนว่าเราต้องการ IOutputProvider และไม่มีอะไรอื่นที่จะทำให้คลาสนี้ใช้งานได้ นี่คือการทดสอบอย่างเต็มที่และมีการพึ่งพาเมื่ออินเตอร์เฟซเดียว ฉันสามารถย้ายคลาสนี้ไปที่ใดก็ได้ในแอปพลิเคชันของฉันรวมถึงโครงการที่แตกต่างกันและทุกอย่างที่ต้องการคือการเข้าถึงอินเทอร์เฟซ IOutputProvider หากผู้พัฒนารายอื่นต้องการเพิ่มสิ่งใหม่ในคลาสนี้ซึ่งต้องการการพึ่งพาครั้งที่สองพวกเขาจะต้องชัดเจนเกี่ยวกับสิ่งที่พวกเขาต้องการในตัวสร้าง
ดูที่คลาสเดียวกันด้วยตัวระบุบริการ:
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
ตอนนี้ฉันได้เพิ่มตัวระบุบริการเป็นการอ้างอิง นี่คือปัญหาที่ชัดเจนทันที:
- ปัญหาแรกของเรื่องนี้คือมันต้องใช้รหัสมากขึ้นเพื่อให้ได้ผลลัพธ์เดียวกัน รหัสเพิ่มเติมไม่ดี มันไม่ได้เป็นรหัสมากขึ้น แต่ก็ยังมีมากขึ้น
- ปัญหาที่สองคือการพึ่งพาของฉันไม่ชัดเจนอีกต่อไป ฉันยังต้องฉีดอะไรบางอย่างในชั้นเรียน ยกเว้นตอนนี้สิ่งที่ฉันต้องการไม่ชัดเจน มันซ่อนอยู่ในทรัพย์สินของสิ่งที่ฉันขอ ตอนนี้ฉันต้องการเข้าถึงทั้ง ServiceLocator และ IOutputProvider ถ้าฉันต้องการย้ายคลาสไปยังชุดประกอบอื่น
- ปัญหาที่สามคือการพึ่งพาเพิ่มเติมสามารถดำเนินการโดยนักพัฒนาอื่นที่ไม่ได้รู้ว่าพวกเขากำลังทำเมื่อพวกเขาเพิ่มรหัสในชั้นเรียน
- ในที่สุดรหัสนี้ยากที่จะทดสอบ (แม้ว่า ServiceLocator เป็นอินเทอร์เฟซ) เพราะเราต้องล้อเลียน ServiceLocator และ IOutputProvider แทนที่จะเป็นเพียง IOutputProvider
ดังนั้นทำไมเราไม่ทำให้ตัวระบุตำแหน่งเซอร์วิสเป็นคลาสแบบสแตติก ลองดู:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
มันง่ายกว่านี้ใช่มั้ย
ไม่ถูกต้อง.
สมมติว่า IOutputProvider ดำเนินการโดยเว็บเซอร์วิสที่ใช้เวลานานซึ่งเขียนสตริงในฐานข้อมูลต่าง ๆ สิบห้าฐานข้อมูลทั่วโลกและใช้เวลานานมากในการทำให้เสร็จสมบูรณ์
ลองทดสอบคลาสนี้กัน เราต้องการการใช้งาน IOutputProvider ที่แตกต่างกันสำหรับการทดสอบ เราจะเขียนแบบทดสอบได้อย่างไร
เราจำเป็นต้องทำการกำหนดค่าแฟนซีบางอย่างในคลาส ServiceLocator เพื่อใช้งาน IOutputProvider ที่แตกต่างกันเมื่อมันถูกเรียกโดยการทดสอบ แม้แต่การเขียนประโยคนั้นก็เจ็บปวด การดำเนินการมันจะทรมานและมันจะเป็นฝันร้ายของการบำรุงรักษา เราไม่จำเป็นต้องแก้ไขคลาสเฉพาะสำหรับการทดสอบโดยเฉพาะถ้าคลาสนั้นไม่ใช่คลาสที่เราพยายามทำการทดสอบ
ดังนั้นตอนนี้คุณเหลืออยู่ a) การทดสอบที่ทำให้เกิดการเปลี่ยนแปลงของรหัสที่ไม่พึงประสงค์ในคลาส ServiceLocator ที่ไม่เกี่ยวข้อง หรือ b) ไม่มีการทดสอบเลย และคุณจะเหลือโซลูชันที่ยืดหยุ่นน้อยลงเช่นกัน
ดังนั้นระดับบริการระบุตำแหน่งมีจะได้รับการฉีดเข้าไปในตัวสร้าง ซึ่งหมายความว่าเรามีปัญหาเฉพาะที่กล่าวถึงก่อนหน้านี้ ตัวระบุตำแหน่งบริการต้องการรหัสเพิ่มเติมบอกผู้พัฒนารายอื่นว่าต้องการสิ่งที่ไม่สนับสนุนให้นักพัฒนารายอื่นเขียนรหัสที่แย่ลงและทำให้เรามีความยืดหยุ่นน้อยลงในการย้ายไปข้างหน้า
ใส่เพียงระบุตำแหน่งบริการเพิ่มการมีเพศสัมพันธ์ในโปรแกรมประยุกต์และส่งเสริมให้นักพัฒนาอื่น ๆ ที่จะเขียนโค้ดควบคู่สูง