2014-01-20 192 views
1

我正在使用SimpleInjector 2.2.3.0。裝飾師裝飾基地命令

我有一個MVC 4.0項目,並在我的命令周圍使用裝飾模式來管理我的UnitOfWork和我的授權。 我有授權的裝飾工 - IAuthorisationDecorator。這包裝了我所有的ITransactionalCommandHandlers。 (每個ITransactionalCommandHandler也被IUnitOfWorkDecorator飾)授權的裝飾看起來是這樣的:

public void Handle(TCommand command) 
{ 
    //authorise this command for this Iprincipal 

    decoratedCommandHandler.Handle(command); 
} 

我現在想創建一個INonTransactionalCommandHandler(這並不需要一個NHibernate的會議,它只是做一些文件IO)。 注意兩個INonTransactionalCommandHandlerICommandHandlerITransactionalCommandHandler繼承 - 它看起來像這樣:

public interface ICommandHandler<in TCommand, out TResult> 
{ 
    TResult Result { get; } 
    void Handle(TCommand command); 
} 

我真的不希望有創建兩個相同AuthorisationDecorators(交易/事務不)。因爲最終他們裝飾了一個基地ICommandHandler(其實施爲ITransactionalINonTransactional)。

所以我的問題是 - 有什麼辦法可以創建1個裝飾是裝飾基地ICommandHandler - 但容器知道到ICommandHandler投高達ITransactionalCommandHandlerINonTransactionalCommandHandler。有沒有辦法做到這一點?

回答

6

如果你通過它們的ICommandHandler<TCommand, TResult>接口註冊了所有的命令處理程序,你不能用一個包裝ITransactionalCommandHandler<TCommand, TResult>的裝飾器來包裝部分處理程序。如果處理程序由該接口明確註冊,則只能應用此修飾程序,但這不是您應該執行的操作。你不想這樣做,因爲這意味着這些命令的使用者需要知道處理程序是否是事務性的,他們不應該關心它(這是一個實現細節)。

您的交易裝飾因此應包裝ICommandHandler<TCommand, TResult>。如果需要訪問接口ITransactionalCommandHandler<TCommand, TResult>,修飾器應該將輸入參數ICommandHandler轉換爲ITransactionalCommandHandler

你需要的是註冊基於謂詞的裝飾,具體如下:

// NOTE: In Simple Injector v2.x use 'ManyForOpenGeneric' instead. 
container.Register(typeof(ICommandHandler<,>), myAssemblies); 

container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(TransactionDecorator<,>), 
    c => typeof(ITransactionalCommandHandler<,>) 
      .MakeGenericType(c.ServiceType.GetGenericArguments()) 
       .IsAssignableFrom(c.ImplementationType)); 

container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(AuthorisationDecorator<,>)); 

謂詞確保只有命令處理程序的裝飾實現了ITransactionalCommandHandler<,>

而不是使用接口,你還可以標記處理程序與一個屬性:

[Transactional] 
class ShipOrderCommandHandler : ICommandHandler<ShipOrderCommand, Unit> { ... } 

在這種情況下,登記將成爲:

container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(TransactionDecorator<,>), 
    c => c.ImplementationType.GetCustomAttribute<TransactionalAttribute>() != null); 

另一個選擇是,以紀念命令本身具有接口或從基類繼承:

public class ShipOrderCommand : ICommand<Unit>, ITransactionalCommand 
{ 
    public Guid OrderId; 
} 

This可以讓你在TransactionDecorator<,>添加一個泛型類型的限制,並允許您卸下RegisterDecorator註冊斷言:

public class TransactionDecorator<int TCommand, out TResult> 
    : ICommandHandler<TCommand, TResult> 
    where TCommand : ITransactionalCommand 
{ 
    // etc 
} 

由於簡單的注射器瞭解到類型限制,您可以簡化註冊如下:

container.RegisterDecorator(typeof(ICommandHandler<,>), typeof(TransactionDecorator<,>)); 

請注意,在這種特殊情況下,我不建議使用最後一種方法,因爲在命令上再次執行ITransactionalCommand接口意味着實現細節泄漏到命令的定義中。命令及其消費者不需要知道命令處理程序是否需要事務處理(並且這在未來可能會發生變化)。

+1

最後一刻落敗後再次! – qujck

+0

謝謝,這很好。我選擇了第一個選項,所以我的一些命令處理程序實現了ITransactionalCommandHandler - 但是現在授權可以是通用的,因爲裝飾器只處理ICommandHandlers。我將事務實現的泄漏點寫入CommandHandler中,但我認爲其他選項會遇到同樣的問題。 – jonho

2

只要可以裝飾你的類這種方式您遵循一個簡單的規則 - 你的消費者總是期待ICommandHandler<,>

所以,如果你的構造是這樣寫的

public class Consumer 
{ 
    public Consumer(ICommandHandler<CommandStub, ResultStub> handler) 
    { 
    } 
} 

你的實現是註冊這樣的事情(即作爲ICommandHandler的)

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<,>), 
    typeof(ICommandHandler<,>).Assembly); 

而且你的裝飾被註冊了諸如t他

container.RegisterDecorator(
    typeof(ICommandHandler<,>), 
    typeof(MockAuthorisationDecorator<,>)); 

那麼儘管的MockTransactionalCommandHandler實施寫成從ITransactionalCommandHandler<,>繼承

public class MockTransactionalCommandHandler : 
    ITransactionalCommandHandler<CommandStub, ResultStub> { 
    public ResultStub Result { get { throw new NotImplementedException(); } } 

    public void Handle(CommandStub command) { } 
} 

Consumer類仍然會給出飾以MockAuthorisationDecorator

實例的 MockTransactionalCommandHandler實例的事實

當你有一個修飾器應該只包裝一些時,會引入一個輕微的複雜性(通過一個簡單的解決方案) 10(在這種情況下,像MockUnitOfWorkDecorator<,>這樣的裝飾器) - 爲了得到這個工作,你需要定義一個Predicate<>作爲裝飾器註冊的一部分。

註冊爲MockUnitOfWorkDecorator會是這個樣子:

container.RegisterDecorator(
    typeof(ICommandHandler<,>), 
    typeof(MockUnitOfWorkDecorator<,>), 
    context => 
    { 
     var baseType = typeof(ITransactionalCommandHandler<,>); 
     var type = baseType.MakeGenericType(
      context.ServiceType.GetGenericArguments()); 
     return type.IsAssignableFrom(context.ImplementationType); 
    });