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 中,您可以对不同类型使用相同的字符串,如上所示。您可以使用它来描述哪些特定的实现在一起。这是我的建议:)
我目前正在开发一个应用程序,该应用程序将根据 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 中,您可以对不同类型使用相同的字符串,如上所示。您可以使用它来描述哪些特定的实现在一起。这是我的建议:)