C#依赖容器和构造函数

CDM*_*CDM 4 c# constructor dependency-injection ioc-container

我花了一些时间记录自己的依赖注入和IoC,但我还没有找到解决问题的方法.

我的问题涉及在使用依赖容器时对象的实例化,因为它创建了对构造函数的参数的依赖.在我遇到的几乎每个例子中,具体类的构造函数都没有任何参数.它使一切都变得"简单".因此我的问题在这里.

我们举一个例子:我需要从两个源A和B下载一些数据.源A包含各种格式的数据; 例如csv和xml.我们不需要为源B指定这样的东西.

这是一些代码(请注意我尽可能地简化代码来说明我的观点):

using System.Net;
using System.IO;
using System.Reflection;

namespace Question
{
  class Program
  {
    static void Main(string[] args)
    {
        //exemple of code using Client A
        DependencyContainer container1 = GetContainer1();
        IClient client1 = container1.Resolve<IClient>("xml");
        User user1 = new User(client1);
        user1.run();

        DependencyContainer container2 = GetContainer2();
        IClient client2 = container2.Resolve<IClient>();
        User user2 = new User(client2);
        user2.run();
    }

    public static DependencyContainer GetContainer1()
    {
        DependencyContainer container = new DependencyContainer();
        container.Register<IClient, ClientA>();
        return container;
    }

    public static DependencyContainer GetContainer2()
    {
        DependencyContainer container = new DependencyContainer();
        container.Register<IClient, ClientB>();
        return container;
    }
}

public class User
{
    private readonly IClient _Client;

    public User(IClient client)
    {
        _Client = client;
    }

    public void run()
    {
        string address = _Client.getAddress();
        string data = _Client.getData(address);
        _Client.writeData(data);
    }
}
// Abstraction
public interface IClient
{
    /// <summary>
    /// create the address or the name of the file storing the data
    /// </summary>
    string getAddress();

    /// <summary>
    /// uses a WebClient to go and get the data at the address indicated
    /// </summary>
    string getData(string adress);

    /// <summary>
    /// Write the data in a local folder
    /// </summary>
    void writeData(string data);
}

//Implementation A
public class ClientA : IClient
{
    // Specify the type of the file to be queried in the database
    // could be a csv or an xml for example
    private readonly string _FileType;

    public ClientA(string fileType)
    {
        _FileType = fileType;
    }

    public string getAddress()
    {
        return "addressOfFileContainingData." + _FileType;
    }

    public string getData(string address)
    {
        string data = string.Empty;
        using (WebClient client = new WebClient())
        {
            data = client.DownloadString(address);
        }
        return data;
    }

    public void writeData(string data)
    {
        string localAddress = "C:/Temp/";
        using (StreamWriter writer = new StreamWriter(localAddress))
        {
            writer.Write(data);
        }
    }
}

//Implementation B
public class ClientB : IClient
{
    public ClientB()
    {
    }

    public string getAddress()
    {
        return "addressOfFileContainingData";
    }

    public string getData(string address)
    {
        string data = string.Empty;
        using (WebClient client = new WebClient())
        {
            data = client.DownloadString(address);
        }
        return data;
    }

    public void writeData(string data)
    {
        string localAddress = "C:/Temp/";
        using (StreamWriter writer = new StreamWriter(localAddress))
        {
            writer.Write(data);
        }
    }
}

public class DependencyContainer
{
    private Dictionary<Type, Type> _Map = new Dictionary<Type, Type>();

    public void Register<TypeToResolve, ResolvedType>()
    {
        _Map.Add(typeof(TypeToResolve), typeof(ResolvedType));
    }

    public T Resolve<T>(params object[] constructorParameters)
    {
        return (T)Resolve(typeof(T), constructorParameters);
    }

    public object Resolve(Type typeToResolve, params object[] constructorParameters)
    {
        Type resolvedType = _Map[typeToResolve];
        ConstructorInfo ctorInfo = resolvedType.GetConstructors().First();
        object retObject = ctorInfo.Invoke(constructorParameters);
        return retObject;
    }
}
Run Code Online (Sandbox Code Playgroud)

}

我倾向于认为这段代码有一些好处,但随意纠正我.但是,实例化:

IClient client = container.Resolve<IClient>("xml");
Run Code Online (Sandbox Code Playgroud)

IClient client = container.Resolve<IClient>();
Run Code Online (Sandbox Code Playgroud)

引起了很多关注.高级模块(此处为User类)不依赖于预期的具体实现.但是,现在类程序依赖于具体类的构造函数的结构!因此,它通过在其他地方创建更大的问题来解决一个问题.我宁愿依赖于具体的实现而不是它的构造函数的结构.让我们假设ClientA的代码被重构并且构造函数被更改,那么我不知道类Program实际上是否使用它.

最后,我的问题:

  1. 我错过了IoC的观点吗?
  2. 我想念它吗?
  3. 如果没有,如何解决这个问题?

一种解决方案是不要在ClientA的构造函数中有任何参数.但这是否意味着构造函数在使用依赖容器时应该永远不会有任何参数?或者这是否意味着在其构造函数中具有参数的对象不适合这种技术?人们还可以争辩说ClientA和ClientB不应该从相同的单一接口派生,因为它们本质上不会以相同的方式运行.

感谢您的意见和建议.

Mar*_*ann 7

我错过了IoC的观点吗?

是的,不是.幸运的是,你的具体类(User,ClientA,ClientB)都依赖于构造器注入,这是最重要的依赖注入(DI)模式.另一方面,DI容器完全是可选的.

因此,使用Pure DI,您只需实现这样的Main方法:

static void Main(string[] args)
{
    //exemple of code using Client A
    User user1 =
       new User(
           new ClientA(
               "xml"));
    user1.run();

    User user2 =
        new User(
            new ClientB());
    user2.run();
}
Run Code Online (Sandbox Code Playgroud)

这不仅易于每个人理解,还可以在撰写对象图时为您提供编译时反馈.

DI最重要的目标是确保实现代码适当地解耦,这是Constructor Injection帮助做的事情.

我想念它吗?

或许,但也不多.如果您希望使用DI容器而不是Pure DI,则应遵循Register Resolve Release模式.如果你想要一个User对象,你应该请求,而不是请求一个IClient对象:

var user = container.Resolve<User>();
user.run();
Run Code Online (Sandbox Code Playgroud)

您可以在容器中正确注册所有服务.如果要使用ClientA,则需要告诉容器它应该用于fileType构造函数参数的值.具体如何操作取决于您使用的特定DI容器.

但是,有时您可以为原始依赖项定义约定,例如从应用程序的配置文件中提取所有原始值.

如果没有,如何解决这个问题?

我的建议是使用上面的Pure DI方法,除非你有令人信服的理由使用DI容器.根据我的经验,这很少发生.