2014-03-12 56 views
5

雖然我一直在使用Ninject,但我一般都很喜歡DI,所以我是使用Simple Injector的全新品牌。吸引我想要使用Simple Injector的一件事是裝飾者的易用性。如何裝飾依賴運行時值創建的類

我已經能夠在所有正常情況下使用Simple Injector成功使用裝飾器,在請求服務時解決了依賴關係。然而,我很難弄清楚在服務必須使用運行時值構建的情況下是否有辦法讓我的裝飾器應用於案例中。

在Ninject中,我可以將ConstructorArgument傳遞給kernel.Get<IService>請求,該請求可以沿着N個裝飾器鏈一路繼承到「真正的」實現類。我無法想出一個方法來複制使用簡單注射器。

我已經在下面說明了一些非常基本的代碼。我想在現實世界中做的事情是將IMyClassFactory實例傳遞給我的應用程序中的其他類。那些其他類可以使用它們創建IMyClass實例,使用它們提供的IRuntimeValue。他們從IMyClassFactory得到的IMyClass實例將被註冊的裝飾器自動裝飾。

我知道我可以在我的IMyClassFactory或任何Func<IMyClass>中手動應用我的裝飾器,但我希望它能「正常工作」。

我一直在試圖抽象出MyClass構造,但我無法弄清楚如何使用IRuntimeValue構造函數參數來解決它並進行裝飾。

我忽略了一個明顯的解決方案嗎?

using System; 
using SimpleInjector; 
using SimpleInjector.Extensions; 

public class MyApp 
{ 
    [STAThread] 
    public static void Main() 
    { 
     var container = new Container(); 
     container.Register<IMyClassFactory, MyClassFactory>(); 
     container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator)); 

     container.Register<Func<IRuntimeValue, IMyClass>>(
       () => r => container.GetInstance<IMyClassFactory>().Create(r)); 

     container.Register<IMyClass>(() => ?????)); // Don't know what to do 

     container.GetInstance<IMyClass>(); // Expect to get decorated class 
    } 
} 

public interface IRuntimeValue 
{ 
} 

public interface IMyClass 
{ 
    IRuntimeValue RuntimeValue { get; } 
} 

public interface IMyClassFactory 
{ 
    IMyClass Create(IRuntimeValue runtimeValue); 
} 

public class MyClassFactory : IMyClassFactory 
{ 
    public IMyClass Create(IRuntimeValue runtimeValue) 
    { 
     return new MyClass(runtimeValue); 
    } 
} 

public class MyClass : IMyClass 
{ 
    private readonly IRuntimeValue _runtimeValue; 

    public MyClass(IRuntimeValue runtimeValue) 
    { 
     _runtimeValue = runtimeValue; 
    } 

    public IRuntimeValue RuntimeValue 
    { 
     get 
     { 
      return _runtimeValue; 
     } 
    } 
} 

public class MyClassDecorator : IMyClass 
{ 
    private readonly IMyClass _inner; 

    public MyClassDecorator(IMyClass inner) 
    { 
     _inner = inner; 
    } 

    public IRuntimeValue RuntimeValue 
    { 
     get 
     { 
      return _inner.RuntimeValue; 
     } 
    } 
} 

編輯1:

好,感謝史蒂芬爲偉大的答案。它給了我一些想法。

雖然不是我的情況,但更多的是「經典」。假設我有一個ICustomer,它是在運行時通過讀取數據庫或從磁盤反序列化或創建的東西創建的。所以我想這將被認爲是一個「新」引用史蒂文連接articles之一。我想創建一個ICustomerViewModel的實例,以便我可以顯示和操作我的ICustomer。我的具體CustomerViewModel類在其構造函數中接受了ICustomer以及可以由容器解析的另一個依賴項。

所以我有一個ICustomerViewModelFactory有一個.Create(ICustomer customer)定義方法返回ICustomerViewModel。我總能得到這個工作之前,我問這個問題,因爲我在執行的ICustomerViewModelFactory我能做到這一點(廠組成根實現):

return new CustomerViewModel(customer, container.GetInstance<IDependency>()); 

我的問題是,我想我的ICustomerViewModel由容器進行裝飾並且將它推到新的地位。現在我知道如何解決這個限制。

所以我想我的後續問題是:我的設計是否在第一個地方錯了?我真的覺得ICustomer應該被傳遞到CustomerViewModel的構造函數中,因爲它演示了它的意圖,它是必需的,得到驗證等。我不想在事後添加它。

回答

6

簡單的噴油器顯然不支持通過GetInstance方法傳遞運行時值。原因是在構建對象圖時不應使用運行時值。換句話說,injectables的構造函數不應該依賴運行時值。這樣做有幾個問題。首先,您的注射劑可能需要比這些運行時間值的壽命長得多。但也許更重要的是,您希望能夠使用verifydiagnose您的容器的配置,並且當您開始在對象圖中使用運行時值時會變得更加麻煩。

所以一般來說有兩個解決方案。要麼通過方法調用圖傳遞運行時值,要麼創建一個「上下文」服務,在請求時可以提供此運行時值。

薪火通過調用圖運行值是特別有效的解決方案時,你的做法像thisthis,你傳遞的信息通過系統或當運行值可以是服務的合同的一個明顯的部分架構。在這種情況下,通過消息或方法傳遞運行時值很容易,並且此運行時值也將在途中通過任何裝飾器。

在您的情況下,這將意味着工廠創建的IMyService而不通過在IRuntimeValue和您的代碼傳遞給使用該方法(或多個)IMyService此值它指定:

var service = _myServiceFactory.Create(); 
service.DoYourThing(runtimeValue); 

通過傳遞通過調用圖表的運行時值並不總是一個好的解決方案。特別是當此運行時值不應該成爲發送消息的合約的一部分時。這特別適用於上下文信息用作關於當前登錄用戶,當前系統時間等的信息。您不希望傳遞此信息;你只是希望它可用。我們不希望這樣做,因爲這會給消費者帶來額外的負擔,每次都要傳遞正確的價值,而他們可能甚至不應該能夠更改這些信息(以用戶的上下文來執行請求實例)。

在這種情況下,您應該定義可注入的服務並允許檢索此上下文。例如:

public interface IUserContext { 
    User CurrentUser { get; } 
} 

public interface ITimeProvider { 
    DateTime Now { get; } 
} 

在這些情況下,當前用戶和當前時間不是直接注入構造函數,而是這些服務是。需要訪問當前用戶的組件可以簡單地調用_userContext.CurrentUser,這將在構造對象後完成(在構造函數中讀取:而不是)。因此:以懶惰的方式。

然而,這並不意味着IRuntimeValue必須在調用MyClass之前的某個位置設置。這可能意味着您需要將其設置在工廠內部。這裏有一個例子:

var container = new Container(); 
var context = new RuntimeValueContext(); 
container.RegisterSingle<RuntimeValueContext>(context); 
container.Register<IMyClassFactory, MyClassFactory>(); 
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator)); 
container.Register<IMyClass, MyClass>(); 

public class RuntimeValueContext { 
    private ThreadLocal<IRuntimeValue> _runtime; 
    public IRuntimeValue RuntimeValue { 
     get { return _runtime.Value; } 
     set { _runtime.Value = value; } 
    } 
} 

public class MyClassFactory : IMyClassFactory { 
    private readonly Container _container; 
    private readonly RuntimeValueContext context; 
    public MyClassFactory(Container container, RuntimeValueContext context) { 
     _container = container; 
     _context = context; 
    } 

    public IMyClass Create(IRuntimeValue runtimeValue) { 
     var instance = _container.GetInstance<IMyClass>(); 
     _context.RuntimeValue = runtimeValue; 
     return instance; 
    } 
} 

public class MyClass : IMyClass { 
    private readonly RuntimeValueContext _context; 
    public MyClass(RuntimeValueContext context) { 
     _context = context; 
    } 
    public IRuntimeValue RuntimeValue { get { return _context.Value; } } 
} 

您也可以讓MyClass接受IRuntimeValue並進行以下注冊:

container.Register<IRuntimeValue>(() => context.Value); 

,但在驗證對象圖中不允許的,因爲簡單的噴油器將確保註冊有去無回null,但默認情況下context.Value將爲空。因此,另一種選擇是做到以下幾點:

container.Register<IMyClass>(() => new MyClass(context.Value)); 

這使得IMyClass登記待考證,但在驗證過程中仍然創造注射有一個空值的新MyClass實例。如果在MyClass構造函數中有一個保護子句,則這將失敗。但是,此註冊不允許MyClass由容器自動連線。例如,當你有更多的依賴關係注入MyClass時,該類可以派上用場。

+1

此外,如果服務取決於運行時值,那麼您已經將一些知識用於服務的實現方面,這是您嘗試避免的一點。如果一個實現需要傳遞數字42,那麼這個數字是否與不同的實現有關? 42是什麼意思? –

+0

@ LasseV.Karlsen我完全同意你的價值類型和類似的東西。但我試圖讓我的腦袋裏說,運行時的價值是ICustomer剛剛從數據庫中讀入,我需要將它(以及一些其他容器解析的依賴關係)傳遞給一個構造函數說一個CustomerViewModel。雖然史蒂文給了我很多想法,但是這是否是正確的設計。 –

+0

這是我消化和思考的很多信息(並且感謝您的鏈接並幫助我陷入成功之坑)。我將添加一個編輯以作進一步評論。實際上我自己想出了你自己的第二個解決方案,但是有點害怕它,因爲1)我從來沒有使用ThreadLocal存儲(整潔),2)我擔心可能存在一些其他的副作用,在上下文中引用對象。但那可能是我走的路。 –