2014-05-08 43 views
3

我有一個應用程序query/handler based architecture。我有以下接口:基於泛型類型約束在Autofac中有條件地應用通用裝飾器

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> 
{ 
    TResult Handle(TQuery query); 
} 

該接口有許多非通用實現。那些實現被通用裝飾器封裝,以用於日誌記錄,分析,授權等。但是,有時候,我想根據裝飾器的泛型類型約束來有條件地應用泛型裝飾器。就拿只能被應用到返回的查詢,這個緩存裝飾一ReadOnlyCollection<T>(僅僅是因爲緩存任何的集合,是可變的並沒有太大的意義):

public class CachingQueryHandlerDecorator<TQuery, TResult> 
    : IQueryHandler<TQuery, ReadOnlyCollection<TResult>> 
    where TQuery : IQuery<ReadOnlyCollection<TResult>> 
{ 
    private readonly IQueryHandler<TQuery, ReadOnlyCollection<TResult>> decoratee; 
    private readonly IQueryCache cache; 

    public CachingQueryHandlerDecorator(
     IQueryHandler<TQuery, ReadOnlyCollection<TResult>> decoratee, 
     IQueryCache cache) 
    { 
     this.decoratee = decoratee; 
     this.cache = cache; 
    } 

    public ReadOnlyCollection<TResult> Handle(TQuery query) 
    { 
     ReadOnlyCollection<TResult> result; 

     if (!this.cache.TryGetResult(query, out result)) 
     { 
      this.cache.Store(query, result = this.decoratee.Handle(query)); 
     } 

     return result; 
    } 
} 

什麼可能使它更棘手的是,那些條件裝飾器可以在裝飾器鏈中的任何地方。他們常常是中間的裝飾者之一。例如,這個CachingQueryHandlerDecorator包裝了一個無條件的ProfilingQueryHandlerDecorator,並且應該被條件SecurityQueryHandlerDecorator包裝。

我發現this answer是指有條件地應用非泛型裝飾器;而不是基於泛型類型約束條件應用泛型裝飾器。我們如何使用Autofac中的通用裝飾器來實現這一點?

+0

可能的重複[在基於配置值的Autofac中有條件地應用通用裝飾器](http://stackoverflow.com/questions/23472611/applying-generic-decorators-conditional-in-autofac-based-on-configuration-valu ) –

+0

@JimBolla:這不是重複的IMO。雖然兩者都是有條件地處理泛型裝飾器,但這個問題明確地涉及泛型類型約束,它很可能需要一種不同的方式來處理它們。 – Steven

+0

答案將完全相同......實施與原始問題中推薦的類似的IRegistrationSource。唯一的區別將在'if'語句中。哎呀,你可以爲這個問題做出實現,另一個共享一個通用的抽象基類。 –

回答

-1

如果我繼承了代碼庫有裝飾鏈,這是我希望看到:

// Think of this as the "master decorator" - all calling code comes through here. 
class QueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> 
{ 
    private readonly IComponentContext context; 

    public QueryHandler(IComponentContext context) 
    { 
     this.context = context; 
    } 

    public TResult Handle(TQuery query) 
    { 
     var handler = context.Resolve<IQueryHandler<TQuery, TResult>>(); 

     if (typeof(TResult).IsClosedTypeOf(typeof(ReadOnlyCollection<>))) 
     { 
      // manual decoration: 
      handler = new CachingQueryHandlerDecorator<TQuery, TResult>(handler); 

      // or, container-assisted decoration: 
      var decoratorFactory = context.Resolve<Func<IQueryHandler<TQuery, TResult>, CachingQueryHandlerDecorator<TQuery, TResult>>>(); 
      handler = decoratorFactory(handler); 
     } 

     if (NeedsAuthorization(query)) { ... } 

     return handler.Handle(query); 
    } 
} 

由於裝修的順序很重要,我希望能夠看到它,並很容易地改變它並根據需要逐步完成。即使我是DI新手,我也可以維護這些代碼。但是,如果你有一堆鑰匙和回調以及容器驅動的魔法,那麼對我來說,維護起來就會困難得多。只因爲你可以使用容器的東西並不意味着你應該

最後,請注意,我的QueryHandler類沒有執行IQueryHandler - 這是故意的。我認爲裝飾者模式「主要是有害的」,因爲很多時候它顛覆了Liskov的替代原則。例如,如果您在任何地方都使用IQueryHandler,則配置錯誤的DI容器可能會忽略授權裝飾器 - 類型系統不會發出抱怨,但是您的應用程序肯定已損壞。出於這個原因,我喜歡將「調用站點抽象」與「實現站點抽象」分開(請參閱事件處理器與事件提升器,其名稱爲another of my answers),並儘可能明確地在中間做任何事情。

+1

感謝您花時間回答我的問題。我不同意你的「裝飾者模式是有害的」陳述(儘管任何模式都可能容易被濫用,這是肯定的)。然而問題與你目前的做法是,它不編譯。這是由泛型類型約束引起的。這是可以解決的,但會變得相當難看。正如你所說的,我們希望有一個可維護的代碼庫。但也許問題是,我被簡單的注射器寵壞了:-(。 – Steven

+0

@Steven - 你提到這個問題是可以解決的,但是你沒有提供解決方案(對你的任何一個問題)。應該是在Autofac上添加一個擴展方法以隱藏背後的令人討厭的細節,但我想知道你最終想出了什麼 – NightOwl888

+0

@ NightOwl888我們能夠通過移動類型從裝飾器中刪除泛型類型約束檢查並轉換到我們的裝飾器代碼,這意味着裝飾器總是被應用,裝飾器中的if語句將決定是否應該應用裝飾後的邏輯,這很容易出錯,非常難看,直到我們遷移到Simple Injector。我們從來沒有投入過一個很好的延伸,因爲我知道如何努力做到這一點是正確的。 – Steven

相關問題