【问题标题】:Are primitive constructor parameters a bad idea when using an IoC Container? [closed]使用 IoC 容器时,原始构造函数参数是不是一个坏主意? [关闭]
【发布时间】:2011-12-31 11:07:39
【问题描述】:

标准新手免责声明:我是 IoC 新手,收到的信号不一。我正在寻找有关以下情况的一些指导。

假设我有以下接口和实现:

public interface IImageFileGenerator
{
    void RenameFiles();
    void CopyFiles();
}

public class ImageFileGenerator : IImageFileGenerator
{
    private readonly IList<IImageLink> _links;
    private readonly string _sourceFolder;
    private readonly string _destinationFolder;
    private readonly int _folderPrefixLength;

    public ImageFileGenerator(IList<IImageLink> links, string sourceFolder, string destinationFolder)
    {
        _links = links;
        _sourceFolder = sourceFolder;
        _destinationFolder = destinationFolder;
        _folderPrefixLength = 4;
    }

    public void RenameFiles()
    {
        // Do stuff, uses all the class fields except destination folder
    }

    public void CopyFiles()
    {
        // Do stuff, also uses the class fields
    }
}

我很困惑是否应该只将接口/依赖项发送给构造函数,创建一些参数对象并将其传递给构造函数,还是保持原样并在解析实例时传递参数。

那么有没有更正确的方法来设置此代码以最好地与 IoC 容器配合使用?与我当前的布局相比,以下哪一项在设计上更受欢迎?

1.

public interface IImageFileGenerator
{
    void RenameFiles(IList<IImageLink> links, string sourceFolder);
    void CopyFiles(IList<IImageLink> links, string sourceFolder, stringDestFolder);
}

public class ImageFileGenerator : IImageFileGenerator
{
    private readonly int _folderPrefixLength;

    public ImageFileGenerator()
    {
        _folderPrefixLength = 4;
    }

    public void RenameFiles(IList<IImageLink> links, string sourceFolder)
    {
        // Do stuff
    }

    public void CopyFiles(IList<IImageLink> links, string sourceFolder, stringDestFolder)
    {
        // Do stuff
    }
}

我不喜欢在两种情况下都传递完全相同的东西(目标文件夹除外)。在 IImageFileGenerator 的当前实现中,我需要执行这两个方法,并且每个方法都需要相同的值。这就是我通过构造函数传入状态的原因。

2.

public interface IImageFileGenerator
{
    void RenameFiles();
    void CopyFiles();
}

public class ImageLinkContext
{
    // various properties to hold the values needed in the
    // ImageFileGenerator implementation.
}

public class ImageFileGenerator : IImageFileGenerator
{
    private readonly IList<IImageLink> _links;
    private readonly string _sourceFolder;
    private readonly string _destinationFolder;
    private readonly int _folderPrefixLength;

    public ImageFileGenerator(ImageLinkContext imageLinkContext)
    {
        // could also use these values directly in the methods 
        // by adding a single ImageLinkContext field and skip 
        // creating the other fields
        _links = imageLinkContext.ImageLinks;
        _sourceFolder = imageLinkContext.Source;
        _destinationFolder = imageLinkContext.Destination;
        _folderPrefixLength = 4;
    }

    public void RenameFiles()
    {
        // Do stuff, uses all the class fields except destination folder
    }

    public void CopyFiles()
    {
        // Do stuff, uses all the class fields
    }
}

这种方法甚至可以调整为 Mark Seemann here 提到的外观服务(以前称为聚合服务)。

我还读到您可以为这些值使用属性并使用属性注入,尽管这似乎不再是首选(autofac 提到构造函数注入是首选... Ninject 我相信甚至删除了版本 2 中的功能)。

另外,我读到您还可以创建一个初始化方法并确保在其中设置属性。

有这么多的选择,当我阅读更多关于这些东西的信息时,我变得越来越困惑。我敢肯定没有明确的正确方法(或者至少对于这个例子来说可能有?),但也许有人可以提供每种方法的优缺点。或者也许还有另一种我完全错过的方法。

我现在意识到这个问题可能有点主观(实际上不仅仅是一个问题),但我希望你能原谅我并提供一些指导。

PS - 我目前正在尝试使用 autofac,以防影响哪种设计可能更适合。

注意:我对目标文件夹的代码做了一些细微的更改...... RenameFiles 不使用它(可能对您的回答有影响)。

【问题讨论】:

标签: c# dependency-injection inversion-of-control ioc-container


【解决方案1】:

好吧,在阅读了 Dependency Injection in .Net 这本书后,我最终重新设计了这本书(我强烈推荐这本书给任何面向对象的开发人员,不仅仅是 .Net 开发人员,也不仅仅是那些对使用 IoC 容器感兴趣的人!)。

我现在在域程序集中获得了以下内容:

public interface IImageFileService
{
    void RenameFiles();
    void CopyFiles(); 
}

public interface IImageLinkMapRepository
{
    IList<ImageLink> GetImageLinks(); 
}

然后在 FileAccess 程序集中,我为这些接口创建了实现:

public class ImageFileService : IImageFileService
{
    public ImageFileService(IImageLinkMapRepository repository)
    {
        // null checks etc. left out for brevity
        _repository = repository;
    }

    public void RenameFiles()
    {
        // rename files, using _repository.GetImageLinks(), which encapsulates
        // enough information for it to do the rename operations without this
        // class needing to know the specific details of the source/dest dirs.
    }

    public void CopyFiles() 
    { 
        // same deal as above
    }
}

所以本质上,我已经在构造函数中消除了对原始类型的需求,至少对于这个类来说是这样。在某些时候,我确实需要这些信息,但它被注入到 ImageLinkMapRepository 中,其中信息更有意义。我使用autofac named parameters 处理注入它们。

所以我想回答我自己的问题,如果它们有意义,原始构造函数参数是一个好主意,但请确保将它们放在它们所属的位置。如果事情似乎不正常,可能可以通过重新考虑设计来改进

【讨论】:

    【解决方案2】:

    在您的示例中,您实际传递的是 依赖项,但此外,该类需要 data 进行操作。

    在您的情况下,听起来RenameFiles()CopyFiles() 方法对传递给它们的参数进行操作。鉴于它们的名称​​我认为ImageFileGenerator 的单个实例上的方法可以使用不同的参数调用。如果这是真的,那么参数应该在方法调用本身而不是构造函数上。

    另一方面,如果在一个实例上 RenameFiles()CopyFiles() 仅使用相同的参数调用一次,则参数将是构造函数的良好候选者。

    我个人会尽量避免 必需 依赖项的属性注入 - 在这种情况下,构造函数注入更合适。

    【讨论】:

    • 目前的用法是两者都只被调用一次并且具有相同的参数值。我们很可能会一直调用RenameFiles,然后立即调用CopyFiles。我想我可以将它们组合成一个公共方法,然后将它们都设为私有。我试图拆分功能以便于进行单元测试。
    • @JasonDown:在那种情况下,我认为您回答了自己的问题。此外,如果您传入的数据确实只有一个操作,您可以将其移动到静态方法中。
    • 绝对值得考虑。考虑负载...实际上很有可能需要另一个接口的实现,它只会复制图像...并且会有不同的源和目标(它将是复制图像,但副本将转换为缩略图大小)。所以也许将参数传递给每个方法是要走的路。
    猜你喜欢
    • 1970-01-01
    • 2013-09-16
    • 2016-09-12
    • 1970-01-01
    • 2015-09-29
    • 2013-08-20
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多