Câu hỏi này đã làm phiền tôi trong vài ngày và cảm giác như một vài thực tiễn mâu thuẫn với nhau.
Thí dụ
Lặp lại 1
public class FooDao : IFooDao
{
private IFooConnection fooConnection;
private IBarConnection barConnection;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooConnection = fooConnection;
this.barConnection = barConnection;
}
public Foo GetFoo(int id)
{
Foo foo = fooConection.get(id);
Bar bar = barConnection.get(foo);
foo.bar = bar;
return foo;
}
}
Bây giờ, khi kiểm tra điều này, tôi sẽ giả mạo IFooConnection và IBarConnection và sử dụng Dependency Injection (DI) khi khởi tạo FooDao.
Tôi có thể thay đổi việc thực hiện, mà không thay đổi chức năng.
Lặp lại 2
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooBuilder = new FooBuilder(fooConnection, barConnection);
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Bây giờ, tôi sẽ không viết trình xây dựng này, nhưng hãy tưởng tượng nó làm điều tương tự mà FooDao đã làm trước đây. Đây chỉ là một cấu trúc lại, vì vậy rõ ràng, điều này không thay đổi chức năng, và vì vậy, thử nghiệm của tôi vẫn vượt qua.
IFooBuilder là Internal, vì nó chỉ tồn tại để làm việc cho thư viện, tức là nó không phải là một phần của API.
Vấn đề duy nhất là, tôi không còn tuân thủ Nghịch đảo phụ thuộc. Nếu tôi viết lại cái này để khắc phục vấn đề đó, nó có thể trông như thế này.
Lặp lại 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Điều này nên làm điều đó. Tôi đã thay đổi hàm tạo, vì vậy thử nghiệm của tôi cần thay đổi để hỗ trợ điều này (hoặc cách khác), đây không phải là vấn đề của tôi.
Để làm việc với DI, FooBuilder và IFooBuilder cần được công khai. Điều này có nghĩa là tôi nên viết một bài kiểm tra cho FooBuilder, vì nó đột nhiên trở thành một phần của API thư viện của tôi. Vấn đề của tôi là, các máy khách của thư viện chỉ nên sử dụng nó thông qua thiết kế API dự định của tôi, IFooDao và các thử nghiệm của tôi hoạt động như các máy khách. Nếu tôi không tuân theo Phụ thuộc đảo ngược, các thử nghiệm và API của tôi sẽ sạch hơn.
Nói cách khác, tất cả những gì tôi quan tâm với tư cách là khách hàng, hoặc là thử nghiệm, là để có được Foo chính xác, chứ không phải cách nó được xây dựng.
Các giải pháp
Tôi có nên đơn giản là không quan tâm, viết các bài kiểm tra cho FooBuilder mặc dù nó chỉ công khai để làm hài lòng DI? - Hỗ trợ lặp 3
Tôi có nên nhận ra rằng việc mở rộng API là một nhược điểm của Nghịch đảo phụ thuộc và rất rõ ràng về lý do tại sao tôi chọn không tuân thủ nó ở đây? - Hỗ trợ lặp 2
Tôi có quá chú trọng đến việc có API sạch không? - Hỗ trợ lặp 3
EDIT: Tôi muốn làm rõ, rằng vấn đề của tôi không phải là "Làm thế nào để kiểm tra nội bộ?", Thay vào đó là một câu như "Tôi có thể giữ nội bộ và vẫn tuân thủ DIP, và tôi có nên không?".
IFooBuilder
cần công khai? FooBuilder
chỉ cần được tiếp xúc với hệ thống DI thông qua một số phương thức "thực hiện mặc định" trả về một IFooBuilder
và do đó có thể duy trì nội bộ.
public
API thư viện và public
để thử nghiệm". Hoặc ở dạng khác "Tôi muốn giữ các phương thức riêng tư để tránh xuất hiện chúng trong API, làm cách nào để kiểm tra các phương thức riêng tư đó?". Các câu trả lời luôn là "sống với API công khai rộng hơn", "sống với các thử nghiệm thông qua API công khai" hoặc "tạo các phương thức internal
và làm cho chúng hiển thị với mã kiểm tra".