【问题标题】:How to use DI container to resolve dependencies in Strategy Pattern?如何使用 DI 容器解决策略模式中的依赖关系?
【发布时间】:2018-11-08 21:41:07
【问题描述】:

我目前正在开发一个应用程序,该应用程序将根据用户输入采取不同的行动。所以我想到了策略模式。下面是我的实现:

我有一些业务逻辑:

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 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 容器)

请指教。

【问题讨论】:

  • 您不使用 DI 容器来解析来自用户的动态输入(任何注入都应在引导后完成)。你可以使用工厂。 DI 可用于注入可用的工厂,以及用于这些工厂的名称。例如,使用 Unity,您可以将工厂注册信息提取到配置文件中。然后,您可以修改从配置文件注册的工厂,而无需重新编译程序,并为每个工厂指定一个唯一的名称。您可以为每个单独的工厂提供您想要的任何名称,不需要是类名。
  • @Ryan:您能否提供有关如何为我的策略注册依赖项的更多详细信息。任何示例代码(即使在 Unity 中)都非常感谢。我添加了我的 ContainerBuilder 代码以使其更清晰。
  • 如果其他人在我之前没有做到,今晚我会整理一个例子。无论如何,您当前的层次结构不需要每个策略的中间接口。您的策略并没有在功能或属性方面添加任何新内容,因此您的基本 IStrategy 接口就是模拟目的所需要的全部内容。
  • “每个策略的中间接口”是什么意思?您是在谈论 IBusinessLogic、IApplicationLogic 吗?我认为这是必要的,因为我想将不同的逻辑类别分成不同的类(TypeABusinessLogic 和 TypeAApplicationLogic 处理不同的逻辑)。它帮助我实现单一职责。
  • 我的意思是你不需要 ITypeAStrategy 或 ITypeBStrategy 接口。这些策略不添加任何功能/属性,因此您可以仅使用基本 IStrategy 接口来模拟它们。

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


【解决方案1】:

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

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

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

接下来我们调整类来实现相关的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");
    }
}

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

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 示例:

[Mock] 为 A 类做业务逻辑

[Mock] 为 Type A 做应用逻辑

[Mock] 为 B 类做业务逻辑

[Mock]为Type B做应用逻辑

未模拟的 DI 示例:

为 A 类做业务逻辑

为 A 类做应用逻辑

为 B 类做业务逻辑

为 B 类做应用逻辑

按回车键退出...

这不是解决问题的唯一方法,但我认为这最直接符合您在 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 中,您可以对不同类型使用相同的字符串,如上所示。您可以使用它来描述哪些特定的实现结合在一起。这是我的建议:)

【讨论】:

  • 感谢瑞恩的帮助。令牌接口思路帮我解决了问题
猜你喜欢
  • 2019-04-27
  • 1970-01-01
  • 1970-01-01
  • 2016-10-11
  • 2020-02-05
  • 2014-02-06
  • 1970-01-01
  • 2013-11-06
  • 1970-01-01
相关资源
最近更新 更多