簡體   English   中英

如何避免服務定位器反模式?

[英]How to avoid Service Locator Anti-Pattern?

我正在嘗試從抽象基類中刪除服務定位器,但我不確定要用什么來替換它。 這是我所得到的一個假例子:

public abstract class MyController : Controller
{
    protected IKernel kernel;
    public MyController(IKernel kernel) { this.kernel = kernel); }

    protected void DoActions(Type[] types)
    {

        MySpecialResolver resolver = new MySpecialResolver(kernel);
        foreach(var type in types)
        {
            IMyServiceInterface instance = resolver.Get(type);
            instance.DoAction();
        }
    }
}

這個問題是派生類的instanciator不知道內核必須具有什么綁定才能使MySpecialResolver不引發異常。

這可能是內在難以處理的,因為我從這里不知道我將要解決哪些類型。 派生類負責創建types參數,但它們在任何地方都不是硬編碼的。 (這些類型基於派生類的組合層次結構中存在的屬性。)

我試圖通過延遲加載代理來解決這個問題,但到目前為止我還沒有想出一個干凈的解決方案。

更新

這里確實存在兩個問題,一個是IoC容器被傳遞給控制器​​,充當服務定位器。 這很容易刪除 - 您可以使用各種技術在調用堆棧中向上或向下移動位置。

第二個問題是困難的問題,如果需求在運行時沒有暴露,如何確保控制器具有必要的服務。 它應該從一開始就很明顯:你做不到! 您將始終依賴於服務定位器的狀態或集合的內容。 在這種特殊情況下,任何數量的小問題都不會解決本文中描述的靜態類型依賴關系的問題。 我認為我最終要做的是將一個Lazy數組傳遞給控制器​​構造函數,並在缺少必需的依賴項時拋出異常。

也許你應該放棄Kernel,Types和MySpecialResolver,讓子類直接使用他們需要的IMyServiceInterface實例來調用DoActions。 讓子類決定他們如何到達這些實例 - 他們應該最了解(或者如果他們不知道究竟哪個人決定需要哪個IMyServiceInterface實例)

我同意@chrisichris和@Mark Seemann。

從控制器中拋棄內核。 我會稍微切換你的解析器組合,以便你的控制器可以刪除對IoC容器的依賴,並允許解析器成為唯一擔心IoC容器的項目。

然后我會讓解析器傳遞給控制器​​的構造函數。 這將使您的控制器更加可測試。

例如:

public interface IMyServiceResolver
{
    List<IMyServiceInterface> Resolve(Type[] types);
}

public class NinjectMyServiceResolver : IMyServiceResolver
{
    private IKernal container = null;

    public NinjectMyServiceResolver(IKernal container)
    {
        this.container = container;
    }

    public List<IMyServiceInterface> Resolve(Type[] types)
    {
        List<IMyServiceInterface> services = new List<IMyServiceInterface>();

        foreach(var type in types)
        {
            IMyServiceInterface instance = container.Get(type);
            services.Add(instance);
        }

        return services;
    }
}

public abstract class MyController : Controller
{
    private IMyServiceResolver resolver = null;

    public MyController(IMyServiceResolver resolver) 
    { 
        this.resolver = resolver;
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

現在您的控制器未連接到特定的IoC容器。 此外,您的控制器更易於測試,因為您可以模擬解析器並且根本不需要IoC容器來進行測試。

或者,如果您無法控制何時實例化控制器,則可以稍微修改它:

public abstract class MyController : Controller
{
    private static IMyServiceResolver resolver = null;

    public static InitializeResolver(IMyServiceResolver resolver)
    {
        MyController.resolver = resolver;
    }

    public MyController() 
    { 
        // Now we support a default constructor
        // since maybe someone else is instantiating this type
        // that we don't control.
    }

    protected void DoActions(Type[] types)
    {
        var services = resolver.Resolve(types);

        foreach(var service in services)
        {
            service.DoAction();
        }
    }
}

然后,您可以在應用程序啟動時調用它來初始化解析器:

MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));

我們這樣做是為了處理在XAML中創建的元素,這些元素需要解析依賴關系但我們想要刪除像服務請求一樣的服務定位器。

請原諒任何語法錯誤:)

我正在撰寫一篇關於在您可能感興趣的視圖模型中使用Service Locator調用重構MVVM應用程序主題的博客文章系列。 第2部分即將推出:)

http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/

在發布這個答案之前,我本來希望得到更多信息,但凱利讓我當場。 :)告訴我把我的代碼放在嘴邊,可以這么說。

就像我在對Kelly的評論中所說的那樣,我不同意將解析器/定位器從靜態實現移動到注入的實現。 我同意ChrisChris的觀點,即派生類型所需的依賴項應該在該類中解析,而不是委托給基類。

也就是說,這是我將如何刪除服務位置...

創建命令界面

首先,我將為特定實現創建一個命令接口。 在這種情況下,使用DoActions方法發送的類型是從屬性生成的,因此我將創建一個IAttributeCommand 我正在為命令添加一個Matches方法,以聲明該命令供某些類型使用。

public interface IAttributeCommand
{
    bool Matches(Type type);
    void Execute();
}

添加命令實現

為了實現接口,我傳入了執行命令所需的特定依賴項(由我的容器解析)。 我在我的Matches方法中添加了一個謂詞,並定義了我的執行行為。

public class MyTypeAttributeCommand : IAttributeCommand
{
    MyDependency dependency;
            SomeOtherDependency otherDependency;

    public MyTypeAttributeCommand (MyDependency dependency, ISomeOtherDependency otherDependency)
    {
        this.dependency = dependency;
                    this.otherDependency = otherDependency
    }

    public bool Matches(Type type)
    {
        return type==typeof(MyType)
    }
    public void Execute()
    {
        // do action using dependency/dependencies
    }
}

使用Container注冊命令

在StructureMap(使用你最喜歡的容器)中,我會像這樣注冊數組:

Scan(s=>
       {
                s.AssembliesFromApplicationBaseDirectory();
                s.AddAllTypesOf<IAttributeCommand>();
                s.WithDefaultConventions();
       } 

根據類型選擇和執行命令

最后,在基類上,我在我的構造函數參數中定義一個IAttributeCommand數組,由IOC容器注入。 當派生類型傳入types數組時,我將根據謂詞執行正確的命令。

public abstract class MyController : Controller
{
    protected IAttributeCommand[] commands;

    public MyController(IAttributeCommand[] commands) { this.commands = commands); }

    protected void DoActions(Type[] types)
    {
        foreach(var type in types)
        {
            var command = commands.FirstOrDefault(x=>x.Matches(type));
            if (command==null) continue;

            command.Execute();
        }
    }
}

如果多個命令可以處理一種類型,則可以更改實現: commands.Where(x=>x.Matches(type)).ToList().ForEach(Execute);

效果是一樣的,但是如何構建類有一個細微的差別。 該類沒有與IOC容器的耦合,也沒有服務位置。 該實現更具可測性,因為可以使用其真正的依賴關系構建類,而無需連接容器/解析器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM