C# 正确使用依赖注入

C# 正确使用依赖注入,c#,dependency-injection,inversion-of-control,C#,Dependency Injection,Inversion Of Control,我和另外两位同事正在努力理解如何最好地设计一个程序。例如,我有一个接口ISoda和多个实现该接口的类,如Coke,Pepsi,DrPepper,等等 我的同事说,最好将这些项像密钥/值对一样放入数据库。例如: Key | Name -------------------------------------- Coke | my.namespace.Coke, MyAssembly Pepsi | my.namespace.Pepsi, MyAssembly D

我和另外两位同事正在努力理解如何最好地设计一个程序。例如,我有一个接口
ISoda
和多个实现该接口的类,如
Coke
Pepsi
DrPepper
,等等

我的同事说,最好将这些项像密钥/值对一样放入数据库。例如:

Key       |  Name
--------------------------------------
Coke      |  my.namespace.Coke, MyAssembly
Pepsi     |  my.namespace.Pepsi, MyAssembly
DrPepper  |  my.namespace.DrPepper, MyAssembly
。。。然后使用XML配置文件将输入映射到正确的密钥,查询数据库中的密钥,然后创建对象

我没有任何具体的理由,但我只是觉得这是一个糟糕的设计,但我不知道该说什么或如何正确地反驳它

我的第二位同事建议我们对这些课程进行微观管理。因此,基本上输入将通过一个switch语句,类似于:

ISoda soda;

switch (input)
{
   case "Coke":
      soda = new Coke();
      break;       
   case "Pepsi":
      soda = new Pepsi();
      break;
   case "DrPepper":
      soda = new DrPepper();
      break;
}
这对我来说似乎有点好,但我仍然认为有更好的方法。在过去的几天里,我一直在阅读有关IoC容器的文章,这似乎是一个很好的解决方案。然而,我对依赖注入和IoC容器还是很陌生,所以我不知道如何正确地支持它。或者我错了,有更好的方法吗?如果是这样,有人能提出更好的方法吗

我可以提出什么样的论点来说服我的同事尝试另一种方法?利/弊是什么?我们为什么要单干

不幸的是,我的同事们非常抗拒改变,所以我正试图找出如何说服他们

编辑:

我的问题似乎有点难理解。我们试图弄清楚的是如何最好地设计一个应用程序,它利用多个接口,并包含许多实现这些接口的具体类型

是的,我知道将这些东西存储在数据库中是个坏主意,但我们根本不知道更好的方法。这就是为什么我要问我们如何做得更好

public void DrinkSoda(string input)
{
   ISoda soda = GetSoda(input);
   soda.Drink();
}
我们如何正确地实现
GetSoda
?我们应该重新考虑整个设计吗?如果像这样传递魔术弦是个坏主意,那么我们应该怎么做呢

用户输入,如“健怡可乐”、“可口可乐”、“可乐石灰”或任何将实例化一种类型的
可口可乐
,因此多个关键字映射到一种类型

很多人都说上面贴的代码不好。我知道这很糟糕。我正在寻找理由向我的同事们陈述,并论证为什么这不好,为什么我们需要改变


如果这个解释仍然难以理解,我深表歉意。我很难描述这种情况,因为我真的不明白如何用语言来表达。

我遇到的关于依赖注入的最好介绍实际上是wiki网站上的

您并没有提供太多关于您试图实现的目标的背景信息,但在我看来,您正在尝试设计一个“插件”体系结构。如果是这样的话——你的感觉是正确的——总之,这两种设计都很可怕。第一个相对较慢(数据库和反射?),在数据库层上有大量不必要的依赖项,并且不会随程序扩展,因为每个新类都必须输入数据库。仅仅使用反射有什么问题

第二种方法是不可扩展的。每次引入一个新类时,都必须更新旧代码,并且必须“了解”要插入的每个对象,这使得第三方无法开发插件。您添加的耦合,即使使用类似定位器模式的东西,也意味着您的代码没有可能的那么灵活


这可能是使用依赖注入的好机会。阅读我链接的文章。关键思想是依赖项注入框架将使用配置文件动态加载您指定的类。这使得交换程序的组件变得非常简单和直接。

老实说,我知道其他人不同意,但我发现将类实例化抽象为XML或数据库是一个可怕的想法。我非常喜欢让代码变得清晰,即使这会牺牲一些灵活性。我看到的缺点是:

  • 很难跟踪代码并找到对象的创建位置
  • 在项目中工作的其他人需要了解您用于实例化对象的专有格式
  • 在小型项目中,您将花费更多的时间实现依赖项注入,实际上您将在类之间切换
  • 直到运行时,您才能知道正在创建什么。这是调试的噩梦,调试器也不会因此喜欢你
事实上,我有点厌倦了听这个特别的编程热潮。它感觉和看起来都很难看,它解决了一个很少有项目需要解决的问题。说真的,你要多久从代码中添加和删除苏打水一次。除非它实际上是每次编译的时候,否则有这么多额外的complxexity就没有什么好处了

我只是想澄清一下,我认为控制反转是一个很好的设计思想。我只是不喜欢外部嵌入代码只是为了实例化,用XML或其他方式


开关盒解决方案很好,因为这是实现工厂模式的一种方法。大多数程序员不难理解它。它的组织也比在代码周围实例化子类更清晰。

每个DI库都有自己的语法。Ninject如下所示:

public class DrinkModule : NinjectModule
{
    public override void Load()
    {
        Bind<IDrinkable>()
            .To<Coke>()
            .Only(When.ContextVariable("Name").EqualTo("Coke"));
        Bind<IDrinkable>()
            .To<Pepsi>()
            .Only(When.ContextVariable("Name").EqualTo("Pepsi"));
        // And so on
    }
}
public类DrinkModule:ninject模块
{
公共覆盖无效负载()
{
绑定()
.至()
.仅当.ContextVariable(“名称”).EqualTo(“可口可乐”);
绑定()
.至()
.仅当.ContextVariable(“名称”).EqualTo(“百事可乐”);
//等等
}
}
当然,,
public class DrinkReflectionModule : NinjectModule
{
    private IEnumerable<Assembly> assemblies;

    public DrinkReflectionModule() : this(null) { }

    public DrinkReflectionModule(IEnumerable<Assembly> assemblies)
    {
        this.assemblies = assemblies ??
            AppDomain.CurrentDomain.GetAssemblies();
    }

    public override void Load()
    {
        foreach (Assembly assembly in assemblies)
            RegisterDrinkables(assembly);
    }

    private void RegisterDrinkables(Assembly assembly)
    {
        foreach (Type t in assembly.GetExportedTypes())
        {
            if (!t.IsPublic || t.IsAbstract || t.IsInterface || t.IsValueType)
                continue;
            if (typeof(IDrinkable).IsAssignableFrom(t))
                RegisterDrinkable(t);
        }
    }

    private void RegisterDrinkable(Type t)
    {
        Bind<IDrinkable>().To(t)
            .Only(When.ContextVariable("Name").EqualTo(t.Name));
    }
}
var pop = kernel.Get<IDrinkable>(With.Parameters.ContextVariable("Name", "Coke"));
public interface ISodaFactory
{
    ISoda Create(string input);
}
public class SodaConsumer
{
    private readonly ISodaFactory sodaFactory;

    public SodaConsumer(ISodaFactory sodaFactory)
    {
        if (sodaFactory == null)
        {
            throw new ArgumentNullException(sodaFactory);
        }

        this.sodaFactory = sodaFactory;
    }

    public void DrinkSoda(string input)   
    {   
        ISoda soda = GetSoda(input);   
        soda.Drink();   
    }

    private ISoda GetSoda(input)
    {
        return this.sodaFactory.Create(input);
    }
}
public interface ISodaCreator
{
    string Name { get; }
    ISoda CreateSoda();
}

public class SodaFactory
{
    private readonly IEnumerable<ISodaCreator> sodaCreators;

    public SodaFactory(IEnumerable<ISodaCreator> sodaCreators)
    {
        this.sodaCreators = sodaCreators;
    }

    public ISoda CreateSodaFromName(string name)
    {
        var creator = this.sodaCreators.FirstOrDefault(x => x.Name == name);
        // TODO: throw "unknown soda" exception if creator null here

        return creator.CreateSoda();
    }
}
public ReflectionSodaCreator : ISodaCreator
{
    private readonly ConstructorInfo constructor;

    public string Name { get; private set; }

    public ReflectionSodaCreator(string name, ConstructorInfo constructor)
    {
        this.Name = name;
        this.constructor = constructor;
    }

    public ISoda CreateSoda()
    {
        return (ISoda)this.constructor.Invoke(new object[0])
    }
}
IEnumerable<ISodaCreator> sodaCreators =
    from type in Assembly.GetExecutingAssembly().GetTypes()
    where type.GetInterface(typeof(ISoda).FullName) != null
    from constructor in type.GetConstructors()
    where constructor.GetParameters().Length == 0
    select new ReflectionSodaCreator(type.Name, constructor);
sodaCreators = new List<ISodaCreator>(sodaCreators); // evaluate only once
var sodaFactory = new SodaFactory(sodaCreators);