Tôi nghĩ rằng cách dễ nhất để hiểu sự khác biệt giữa hai loại và tại sao một container DI lại tốt hơn nhiều so với bộ định vị dịch vụ là nghĩ về lý do tại sao chúng ta lại đảo ngược phụ thuộc ngay từ đầu.
Chúng tôi thực hiện đảo ngược phụ thuộc để mỗi lớp nêu rõ chính xác những gì nó phụ thuộc vào hoạt động. Chúng tôi làm như vậy bởi vì điều này tạo ra khớp nối lỏng lẻo nhất mà chúng tôi có thể đạt được. Khớp nối càng lỏng lẻo, càng dễ kiểm tra và tái cấu trúc (và thường yêu cầu tái cấu trúc ít nhất trong tương lai vì mã sạch hơn).
Hãy xem lớp sau:
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);
}
}
Trong lớp này, chúng tôi tuyên bố rõ ràng rằng chúng tôi cần một IOutputProvider và không có gì khác để làm cho lớp này hoạt động. Điều này là hoàn toàn có thể kiểm tra và có một sự phụ thuộc vào một giao diện duy nhất. Tôi có thể di chuyển lớp này đến bất kỳ nơi nào trong ứng dụng của mình, bao gồm một dự án khác và tất cả những gì nó cần là truy cập vào giao diện IOutputProvider. Nếu các nhà phát triển khác muốn thêm một cái gì đó mới vào lớp này, đòi hỏi sự phụ thuộc thứ hai, họ phải rõ ràng về những gì họ cần trong hàm tạo.
Hãy xem cùng một lớp với một bộ định vị dịch vụ:
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);
}
}
Bây giờ tôi đã thêm bộ định vị dịch vụ làm phụ thuộc. Dưới đây là những vấn đề rõ ràng ngay lập tức:
- Vấn đề đầu tiên với điều này là cần nhiều mã hơn để đạt được kết quả tương tự. Thêm mã là xấu. Nó không nhiều mã hơn nhưng nó vẫn còn nhiều hơn.
- Vấn đề thứ hai là sự phụ thuộc của tôi không còn rõ ràng nữa . Tôi vẫn cần phải tiêm một cái gì đó vào lớp. Ngoại trừ bây giờ điều tôi muốn không rõ ràng. Nó được giấu trong một tài sản của thứ mà tôi yêu cầu. Bây giờ tôi cần quyền truy cập vào cả ServiceLocator và IOutputProvider nếu tôi muốn chuyển lớp sang một hội đồng khác.
- Vấn đề thứ ba là một nhà phát triển phụ thuộc khác có thể được thực hiện bởi một nhà phát triển khác thậm chí không nhận ra họ đang lấy nó khi họ thêm mã vào lớp.
- Cuối cùng, mã này khó kiểm tra hơn (ngay cả khi ServiceLocator là giao diện) vì chúng tôi phải giả định ServiceLocator và IOutputProvider thay vì chỉ IOutputProvider
Vậy tại sao chúng ta không biến trình định vị dịch vụ thành một lớp tĩnh? Hãy xem:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Điều này đơn giản hơn nhiều, phải không?
Sai lầm.
Giả sử IOutputProvider được triển khai bởi một dịch vụ web chạy rất dài, viết chuỗi trong mười lăm cơ sở dữ liệu khác nhau trên khắp thế giới và mất một thời gian rất dài để hoàn thành.
Hãy thử kiểm tra lớp này. Chúng tôi cần một triển khai khác của IOutputProvider cho thử nghiệm. Làm thế nào để chúng tôi viết bài kiểm tra?
Để làm được điều đó, chúng ta cần thực hiện một số cấu hình ưa thích trong lớp ServiceLocator tĩnh để sử dụng một triển khai IOutputProvider khác khi nó đang được thử nghiệm gọi. Ngay cả viết câu đó cũng đau đớn. Thực hiện nó sẽ là cực hình và nó sẽ là một cơn ác mộng bảo trì . Chúng ta không bao giờ cần phải sửa đổi một lớp cụ thể để kiểm tra, đặc biệt nếu lớp đó không phải là lớp chúng ta thực sự đang cố kiểm tra.
Vì vậy, bây giờ bạn còn lại với một) một bài kiểm tra gây ra thay đổi mã gây khó chịu trong lớp ServiceLocator không liên quan; hoặc b) không có bài kiểm tra nào cả. Và bạn còn lại với một giải pháp kém linh hoạt là tốt.
Vì vậy, các lớp học định vị dịch vụ có được tiêm vào các nhà xây dựng. Điều đó có nghĩa là chúng ta còn lại với các vấn đề cụ thể được đề cập trước đó. Trình định vị dịch vụ yêu cầu nhiều mã hơn, nói với các nhà phát triển khác rằng họ cần những thứ không cần, khuyến khích các nhà phát triển khác viết mã tệ hơn và cho chúng tôi ít linh hoạt hơn khi chuyển tiếp.
Đặt các trình định vị dịch vụ đơn giản là tăng khả năng ghép nối trong một ứng dụng và khuyến khích các nhà phát triển khác viết mã được ghép cao .