如何实现与每个实现的附加参数/信息的接口

use*_*989 10 c# dependency-injection interface

我的MVC webapp允许用户添加和删除图像.UI调用ImageService.SaveImage(...)我的业务层,内部使用一个标志告诉方法保存到Azure或文件系统.我最终可能会添加S3,我认为这里的界面效果很好.

这就是我在ImageService类中对代码进行映像的方法.它不关心文件的保存方式或位置.

// Service the UI uses
public static class ImageService
{
    public static void SaveImage(byte[] data, IImageProvider imageProvider)
    {
        string fileName = "some_generated_name.jpg"
        imageProvider.Save(fileName, data);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我创建了这些实现

public interface IImageProvider
{
    void Save(string filename, byte[] imageData);
    byte[] Get(string filename);
    void Delete(string filename);
}

// File system implementation
public class FSImageProvider : IImageProvider
{
    public void Delete(string filename)
    {
        File.Delete(filename);
    }

    public byte[] Get( filename)
    {
        return File.ReadAllBytes(filename);
    }

    public void Save(string filename, byte[] imageData)
    {
        File.WriteAllBytes(filename, imageData);
    }
}

// Azure implementation
public class AzureBlobImageProvider : IImageProvider
{
    private string _azureKey = "";
    private string _storageAccountName = "";

    public AzureBlobImageProvider(string azureKey, string storageAccount)
    {
        _azureKey = azureKey;
        _storageAccountName = storageAccount;
    }

    public void Delete(string filename)
    {
        throw new NotImplementedException();
    }

    public byte[] Get(string filename)
    {
        throw new NotImplementedException();
    }

    public void Save(string filename, byte[] imageData)
    {
        throw new NotImplementedException();
    }
}
Run Code Online (Sandbox Code Playgroud)

问题1)传递每个提供商可能需要的其他信息的最佳方式是什么?即Azure需要知道容器名称,blob名称(文件名)和storageAccount名称.S3可能还需要更多.一个很好的例子是文件路径.对于每个提供商而言可能不同,或者根本不存在.Azure需要容器名称,文件系统需要目录名称.如果每个提供商的不同之处,我如何将其添加到界面?

问题2)我应该使用依赖注入来解析业务层中ImageService类中的接口,还是应该在UI中解析它并将其传递给类?

eoc*_*ron 5

首先,将IImageProvider传递到SaveImage方法具有主要的体系结构缺点。在需要跨方法控制IImageProvider 的生命周期的情况下,您需要这种函数签名。在您的情况下,您只是保存图像并且几乎不关心任何类的任何生命周期,但仍然使用这种方法并且最终会使您的代码变得混乱,呃 - 您甚至不特别关心这个提供者(这就是为什么您将它包装到我认为的界面中的原因)

问你自己:

“我的 IImageProvider 实际上是否在 ImageService 之外的任何地方使用?如果没有,为什么每个人(方法、类)甚至需要知道它的存在?”

其次,不是创建提供者——让你的ImageService简单类(删除静态),为其定义接口,并为 Azure/FS/等实现。对于具体实施使用工厂:

public interface IImageService
{
    void SaveImage(byte[] bytes);
}

public interface IImageServiceFactory
{
    IImageService Create(/*here goes enum, string, connections strings, etc*/);
}


internal sealed class AzureImageService : IImageService {/*implmentation*/}
internal sealed class FileSystemImageService : IImageService {/*implmentation*/}
Run Code Online (Sandbox Code Playgroud)

总体

不要在方法中传递依赖项。您的方法应该看起来很简单,没有像ILoggerIImageProvider等您认为可以很好地传递到内部的任何杂乱无章的东西。如果您在某些实现中的某个时候需要它们 - 只需创建通过构造函数获取所需的所有依赖项的类(因此静态修饰符几乎总是被禁止并且仅用于语言扩展)。您可以更轻松地管理您的依赖项并重构您的代码,而不会因在甚至不需要的地方不断重用相同的接口而造成任何附带损害。


Sam*_*eff 4

通常这种类型的实现特定数据将在具体类构造函数中提供,就像您在示例中所做的那样。

我不会在 UI 中创建具体实例。您可以使用依赖项注入,也可以使用一组工厂类来创建具有正确配置的实例。关键点是您希望将这些服务的配置集中到一个位置,而不是将特定于实现的代码散布在整个应用程序中。