Strategy Pattern中如何使用DI容器解决依赖关系?

How to use DI container to resolve dependencies in Strategy Pattern?

我目前正在开发一个应用程序,该应用程序将根据 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)
   {}
}

现在是我的语境class

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)直接依赖于具体的 class(TypeABusinessLogic、TypeAApplicationLogic、TypeBBusinessLogic、TypeBApplicationLogic),这并不好。我无法在单元测试中模拟这些依赖项。

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

请指教

所以我想到了一些方法来解决这个问题,但我认为最干净的方法是只引入一些令牌接口。令牌接口是一种不添加任何属性或功能的接口。例如:

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

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

接下来我们调整classes实现相关token接口:

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");
    }
}

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

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 的注入更容易一些,给每个策略一个名称 属性(你不需要这样做):

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();
    }

这是我的输出:

Mock DI Example:

[Mock] Do Business Logic for Type A

[Mock] Do Application Logic for Type A

[Mock] Do Business Logic for Type B

[Mock] Do Application Logic for Type B

Unmocked DI Example:

Do Business Logic for Type A

Do Application Logic for Type A

Do Business Logic for Type B

Do Application Logic for Type B

Press enter to exit...

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

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

        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();

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