2013-03-11 24 views
6

Tôi đang sử dụng Simple Injector, nhưng có lẽ những gì tôi cần là một câu trả lời khái niệm hơn.Làm cách nào để sử dụng tính năng tiêm phụ thuộc, lấy cấu hình từ nhiều nguồn?

Dưới đây là thỏa thuận này, giả sử tôi có một giao diện với các thiết lập ứng dụng của tôi:

public interface IApplicationSettings 
{ 
    bool EnableLogging { get; } 
    bool CopyLocal { get; } 
    string ServerName { get; } 
} 

Sau đó, người ta thường sẽ có một lớp mà thực hiện IApplicationSettings, nhận được từng lĩnh vực từ một nguồn cụ thể, ví dụ:

public class AppConfigSettings : IApplicationSettings 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; 
      } 
      return enableLogging; 
     } 
    } 
    ... 
} 

HOWEVER! Giả sử tôi muốn lấy EnableLogging từ app.config, CopyLocal từ cơ sở dữ liệu và ServerName từ triển khai khác nhận tên máy tính hiện tại. Tôi muốn có thể trộn kết hợp với cấu hình ứng dụng của mình mà không cần phải tạo 9 lần triển khai, một cho mỗi kết hợp.

Tôi giả định rằng tôi không thể chuyển bất kỳ thông số nào vì các giao diện được giải quyết bởi bộ phun (vùng chứa).

Tôi nghĩ về điều này, ban đầu:

public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
where TEnableLogging : IGetValue<bool> 
where TCopyLocal : IGetValue<bool> 
where TServerName : IGetValue<string> 
{ 
    TEnableLogging EnableLog{get;} 
    TCopyLocal CopyLocal{get;} 
    TServerName ServerName{get;} 
} 

public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName> 
{ 
    private bool? enableLogging; 
    public bool EnableLogging 
    { 
     get 
     { 
      if (enableLogging == null) 
      { 
       enableLogging = Container.GetInstance<TEnableLogging>().Value 
      } 
      return enableLogging; 
     } 
    } 
} 

Tuy nhiên, với điều này tôi có một vấn đề chính: Làm sao tôi biết làm thế nào để tạo ra một thể hiện của TEnableLogging (mà là một IGetValue<bool>)? Giả sử rằng IGetValue<bool> là một giao diện có thuộc tính Giá trị, sẽ được lớp bê tông triển khai thực hiện. Nhưng lớp bê tông có thể cần một số chi tiết cụ thể (như tên của khóa trong app.config) hay không (tôi có thể chỉ muốn trả về luôn đúng).

Tôi tương đối mới để tiêm phụ thuộc, vì vậy có thể tôi đang suy nghĩ sai. Có ai có bất kỳ ý tưởng về cách thực hiện điều này?

(Bạn có thể trả lời sử dụng một thư viện DI, tôi sẽ không quan tâm. Tôi nghĩ rằng tôi chỉ cần lấy các khái niệm về nó.)

Trả lời

11

Bạn chắc chắn tiêu đề một cách sai lầm ở đây.

Một số năm trước, tôi đã tạo một ứng dụng có giao diện giống như IApplicationSettings của bạn. Tôi tin rằng tôi đặt tên nó là IApplicationConfiguration, nhưng nó cũng chứa tất cả các giá trị cấu hình của ứng dụng.

Mặc dù nó đã giúp tôi làm cho ứng dụng của tôi có thể thử nghiệm lúc đầu, sau một thời gian thiết kế bắt đầu cản trở. Rất nhiều triển khai phụ thuộc vào giao diện đó, nhưng nó tiếp tục thay đổi rất nhiều và với nó việc thực hiện, và phiên bản thử nghiệm.

Cũng giống như bạn tôi đã thực hiện một số tải chậm, nhưng điều này đã có một bên khủng khiếp xuống. Khi một trong các giá trị cấu hình bị thiếu, tôi chỉ phát hiện ra rằng nó đã được thực hiện khi giá trị được gọi lần đầu tiên. Điều này dẫn đến một cấu hình là hard to verify.

Nó đã cho tôi một vài lần lặp lại việc tái cấu trúc cốt lõi của vấn đề là gì. Giao diện lớn là một vấn đề. Lớp học IApplicationConfiguration của tôi vi phạm số Interface Segregation Principle và kết quả là khả năng bảo trì kém.

Cuối cùng, tôi phát hiện ra rằng giao diện này hoàn toàn vô dụng.Bên cạnh việc vi phạm ISP, các giá trị cấu hình này mô tả chi tiết triển khai và thay vì tạo ra một ứng dụng trừu tượng rộng rãi, tốt hơn hết là cung cấp trực tiếp mỗi lần thực hiện với giá trị cấu hình cần thiết.

Khi bạn làm điều này, điều dễ nhất cần làm là chuyển giá trị cấu hình đó dưới dạng giá trị thuộc tính. Với Injector đơn giản bạn có thể sử dụng phương pháp RegisterInitializer cho việc này:

var enableLogging = 
    Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); 

container.RegisterInitializer<Logger>(logger => 
{ 
    logger.EnableLogging = enableLogging; 
}); 

Khi làm điều này, giá trị enableLogging được đọc chỉ một lần từ các tập tin cấu hình và được làm như vậy trong quá trình startup ứng dụng. Điều này làm cho nó nhanh chóng và làm cho nó thất bại khi khởi động ứng dụng khi giá trị bị thiếu.

Nếu vì một số lý do bạn cần phải trì hoãn việc đọc (từ cơ sở dữ liệu chẳng hạn), bạn có thể sử dụng Lazy<T>:

Lazy<bool> copyLocal = new Lazy<bool>(() => 
    container.GetInstance<IDatabaseManager>().RunQuery(CopyLocalQuery)); 

container.RegisterInitializer<FileCopier>(copier => 
{ 
    copier.CopyLocal = copyLocal.Value; 
}); 

Thay vì đi qua các giá trị sử dụng tài sản, bạn cũng có thể sử dụng đối số nhà xây dựng, nhưng điều này là một chút khó khăn hơn để đạt được. Hãy chụp look at this article để biết một số ý tưởng.

Nếu bạn thấy mình đăng ký cùng một giá trị cấu hình cho nhiều đăng ký, có thể bạn đang thiếu một trừu tượng. Hãy xem này:

container.RegisterInitializer<UserRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<OrderRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<CustomerRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 
container.RegisterInitializer<DocumentRepository>(rep => { 
    rep.ConnectionString = connectionString; }); 

Trong trường hợp này có lẽ bạn đang thiếu một IDatabaseFactory hoặc IDatabaseManager trừu tượng, và bạn nên làm một cái gì đó như thế này:

container.RegisterSingle<IDatabaseFactory>(new SqlDatabaseFactory(connectionString)); 
+1

Cảm ơn bạn, khiến rất nhiều ý nghĩa! Thú vị là cả hai chúng tôi đã thực hiện các bước tương tự và nhận ra một điều gì đó đang diễn ra theo cách mà nó nên là haha –