如何使用DI容器解析策略模式中的依赖关系?

时间:2018-11-08 21:41:08

标签: c# design-patterns dependency-injection autofac ioc-container

我目前正在开发一个基于userInput行为不同的应用程序。所以我考虑了战略模式。下面是我的实现:

我有一些业务逻辑:

interface IBusinessLogic
{
   void DoBusinessLogic();
}

class TypeABusinessLogic : IBusinessLogic
{
   public void DoBusinessLogic()
   {
      Console.WriteLine("Do Business Logic for Type A");
   }
 } 

class TypeBBusinessLogic : IBusinessLogic
{
   public void DoBusinessLogic()
   {
      Console.WriteLine("Do Business Logic for Type B");
   }
}  

还有一些应用程序逻辑:

interface IApplicationLogic
{
   void DoApplicationLogic();
}

class TypeAApplicationLogic : IApplicationLogic
{
   public void DoApplicationLogic()
   {
      Console.WriteLine("Do Application Logic for Type A");
   }
 } 

class TypeBApplicationLogic : IApplicationLogic
{
   public void DoApplicationLogic()
   {
      Console.WriteLine("Do Application Logic for Type B");
   }
}    

现在,我的策略需要同时进行业务逻辑和应用程序逻辑

interface IStrategy
{
   void DoWork();
}

abstract class StrategyBase : IStrategy
{
   private IBusinessLogic _businessLogic;
   private IApplicationLogic _applicationLogic;

   protected StrategyBase(IBusinessLogic businessLogic, IApplicationLogic applicationLogic)
   {
      _businessLogic = businessLogic;
      _applicationLogic = applicationLogic;
   }

   public void DoWork()
   {
      _businessLogic.DoBusinessLogic();
      _applicationLogic.DoApplicationLogic();
   }
}

class TypeAStrategy : IStrategy
{
   public TypeAStrategy(TypeABussinessLogic businessLogic, TypeAApplicationLogic applicationLogic) : base(businessLogic, applicationLogic)
   {}
}

class TypeBStrategy : IStrategy
{
   public TypeBStrategy(TypeBBussinessLogic businessLogic, TypeBApplicationLogic applicationLogic) : base(businessLogic, applicationLogic)
   {}
}

现在是我的Context类

class Context
{
   private Func<string, IStrategy> _strategyFactory;
   public Context(Func<string, IStrategy> strategyFactory)
   {
      _strategyFactory = strategyFactory;
   } 
   public void Run()
   {
      string userInput = GetUserInput(); //"TypeA" or "TypeB"
      IStrategy strategy = _strategyFactory(userInput);
      strategy.DoWork();
   }
}

这是我的DI构建器代码:

var builder = new ContainerBuilder();
builder.RegisterType<TypeAStrategy>().As<IStrategy>().Keyed<IStrategy>("TypeA");
var builder = new ContainerBuilder();
builder.RegisterType<TypeBStrategy>().As<IStrategy>().Keyed<IStrategy>("TypeB");
builder.Register<Func<string, IStrategy>>( c => 
{
   var componentContext = c.Resolve<IComponentContext>();
   return (key) =>
   {
       IStrategy stategy = componentContext.ResolveKeyed<IStrategy >(key);
       return stategy;
   };
});

我在这里看到的问题是我的策略(TypeAStrategy,TypeBStrategy)直接依赖于具体的类(TypeABusinessLogic,TypeAApplicationLogic,TypeBBusinessLogic,TypeBApplicationLogic),这不好。我无法在单元测试中嘲笑这些依赖项。

如果我让我的策略依赖于接口,那么我将不知道如何实现DI容器来解决依赖关系(注意:我目前正在使用Autofac,但是我可以使用任何其他DI容器)

请告知。

1 个答案:

答案 0 :(得分:1)

因此,我想到了几种方法来实现此目的,但我认为最干净的方法是仅引入一些令牌接口。令牌接口是不添加任何属性或功能的接口。例如:

interface IBusinessLogic
{
    void DoBusinessLogic();
}
interface ITypeABusinessLogic : IBusinessLogic { }
interface ITypeBBusinessLogic : IBusinessLogic { }

interface IApplicationLogic
{
    void DoApplicationLogic();
}
interface ITypeAApplicationLogic : IApplicationLogic { }
interface ITypeBApplicationLogic : IApplicationLogic { }

接下来,我们调整类以实现相关的令牌接口:

class TypeABusinessLogic : ITypeABusinessLogic
{
    public virtual void DoBusinessLogic()
    {
        Console.WriteLine("Do Business Logic for Type A");
    }
}

class TypeBBusinessLogic : ITypeBBusinessLogic
{
    public virtual void DoBusinessLogic()
    {
        Console.WriteLine("Do Business Logic for Type B");
    }
}

class TypeAApplicationLogic : ITypeAApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("Do Application Logic for Type A");
    }
}

class TypeBApplicationLogic : ITypeBApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("Do Application Logic for Type B");
    }
}

我们可以通过实现相关的令牌接口类似地创建模拟类:

class MockTypeABusinessLogic : ITypeABusinessLogic
{
    public void DoBusinessLogic()
    {
        Console.WriteLine("[Mock] Do Business Logic for Type A");
    }
}

class MockTypeBBusinessLogic : ITypeBBusinessLogic
{
    public void DoBusinessLogic()
    {
        Console.WriteLine("[Mock] Do Business Logic for Type B");
    }
}

class MockTypeAApplicationLogic : ITypeAApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("[Mock] Do Application Logic for Type A");
    }
}

class MockTypeBApplicationLogic : ITypeBApplicationLogic
{
    public void DoApplicationLogic()
    {
        Console.WriteLine("[Mock] Do Application Logic for Type B");
    }
}

我还修改了IStrategy界面,使使用Unity的注入更加容易,为每个策略提供了Name属性(您无需这样做):

interface IStrategy
{
    string Name { get;  }
    void DoWork();
}

abstract class StrategyBase : IStrategy
{
    private IBusinessLogic _businessLogic;
    private IApplicationLogic _applicationLogic;

    public string Name { get; private set; }

    protected StrategyBase(String name, IBusinessLogic businessLogic, IApplicationLogic applicationLogic)
    {
        this.Name = name;
        _businessLogic = businessLogic;
        _applicationLogic = applicationLogic;
    }

    public void DoWork()
    {
        _businessLogic.DoBusinessLogic();
        _applicationLogic.DoApplicationLogic();
    }
}    

class TypeAStrategy : StrategyBase
{
    public TypeAStrategy(String name, ITypeABusinessLogic businessLogic, ITypeAApplicationLogic applicationLogic) : base(name, businessLogic, applicationLogic)
    { }
}

class TypeBStrategy : StrategyBase
{
    public TypeBStrategy(String name, ITypeBBusinessLogic businessLogic, ITypeBApplicationLogic applicationLogic) : base(name, businessLogic, applicationLogic)
    { }
}

使用Unity我编写了以下程序来测试注册:

class Context
{
    private Dictionary<string, IStrategy> _strategyFactory = new Dictionary<string, IStrategy>();
    public Context(IStrategy[] strategies)
    {
        foreach (var s in strategies)
        {
            _strategyFactory.Add(s.Name, s);
        }
    }
    public void Run()
    {
        string userInput = "TypeA";
        IStrategy strategy = _strategyFactory[userInput];
        strategy.DoWork();

        userInput = "TypeB";
        strategy = _strategyFactory[userInput];
        strategy.DoWork();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Mock DI Example: ");
        UnityContainer ioc = new UnityContainer();

        ioc.RegisterType<ITypeABusinessLogic, MockTypeABusinessLogic>();
        ioc.RegisterType<ITypeAApplicationLogic, MockTypeAApplicationLogic>();
        ioc.RegisterType<ITypeBBusinessLogic, MockTypeBBusinessLogic>();
        ioc.RegisterType<ITypeBApplicationLogic, MockTypeBApplicationLogic>();
        ioc.RegisterType<IStrategy, TypeAStrategy>("TypeA", new InjectionConstructor("TypeA", typeof(ITypeABusinessLogic), typeof(ITypeAApplicationLogic)));
        ioc.RegisterType<IStrategy, TypeBStrategy>("TypeB", new InjectionConstructor("TypeB", typeof(ITypeBBusinessLogic), typeof(ITypeBApplicationLogic)));

        Context c = ioc.Resolve<Context>();
        c.Run();

        Console.WriteLine("\nUnmocked DI Example: ");

        ioc = new UnityContainer();

        ioc.RegisterType<ITypeABusinessLogic, TypeABusinessLogic>();
        ioc.RegisterType<ITypeAApplicationLogic, TypeAApplicationLogic>();
        ioc.RegisterType<ITypeBBusinessLogic, TypeBBusinessLogic>();
        ioc.RegisterType<ITypeBApplicationLogic, TypeBApplicationLogic>();
        ioc.RegisterType<IStrategy, TypeAStrategy>("TypeA", new InjectionConstructor("TypeA", typeof(ITypeABusinessLogic), typeof(ITypeAApplicationLogic)));
        ioc.RegisterType<IStrategy, TypeBStrategy>("TypeB", new InjectionConstructor("TypeB", typeof(ITypeBBusinessLogic), typeof(ITypeBApplicationLogic)));

        c = ioc.Resolve<Context>();
        c.Run();

        Console.WriteLine("\nPress enter to exit...");
        Console.ReadLine();
    }

这是我的输出:

  

模拟DI示例:

     

[模拟]为类型A做业务逻辑

     

[模拟]为类型A做应用逻辑

     

[模拟]为类型B做业务逻辑

     

[模拟]为类型B做应用逻辑

     

未经模拟的DI示例:

     

为类型A做业务逻辑

     

对类型A执行应用逻辑

     

为类型B做业务逻辑

     

对类型B执行应用逻辑

     

按Enter退出...

这不是解决问题的唯一方法,但是我认为这与您在OP中构造代码的方式最直接匹配。希望这会有所帮助:)

编辑:这是我认为您应该考虑的上述替代方法。它将大大减少您的对象和接口层次结构。注意:您需要使StrategyBase类不抽象,并将构造函数公开。

        Console.WriteLine("\nAlternative DI Example: ");

        ioc = new UnityContainer();

        ioc.RegisterType<IBusinessLogic, TypeABusinessLogic>("TypeA");
        ioc.RegisterType<IApplicationLogic, TypeAApplicationLogic>("TypeA");
        ioc.RegisterType<IStrategy, StrategyBase>("TypeA", new InjectionConstructor("TypeA", new ResolvedParameter<IBusinessLogic>("TypeA"), new ResolvedParameter<IApplicationLogic>("TypeA") ));
        ioc.RegisterType<IBusinessLogic, TypeBBusinessLogic>("TypeB");
        ioc.RegisterType<IApplicationLogic, TypeBApplicationLogic>("TypeB");
        ioc.RegisterType<IStrategy, StrategyBase>("TypeB", new InjectionConstructor("TypeB", new ResolvedParameter<IBusinessLogic>("TypeB"), new ResolvedParameter<IApplicationLogic>("TypeB")));
        c = ioc.Resolve<Context>();
        c.Run();

由于您的类和令牌接口实际上并未为您提供任何功能,因此它们仅用作区分特定实现的一种方法。但是,DI容器已经具有执行此操作的简单方法:字符串。在Unity中,您可以将相同的字符串用于不同的类型,如上所述。您可以使用它来描述哪些特定实现。这是我的建议:)