【问题标题】:Dependency Injection, don't know where to place a method依赖注入,不知道在哪里放置方法
【发布时间】:2016-04-06 17:00:24
【问题描述】:

我创建了一个ILogger 接口 和一个实现ILoggerListViewLogger 类,现在ILogger 有一个不应该存在的方法(SetListViewReference),因为不是全部记录器将记录到 ListView,我不知道将它放在哪里,因为我正在做 依赖注入 我需要那个方法,所以我应该怎么做?谢谢!

using System;
using System.Windows.Forms;

namespace AutoTweet
{
    public interface ILogger
    {
        void Log(string text);

        void SetListViewReference(ListView listview);
    }

    public class ListViewLogger : ILogger
    {
        private ListView _lvLog;

        public void Log(string text)
        {
            _lvLog?.Items.Add(makeLvi(text));
        }

        public void SetListViewReference(ListView listview)
        {
            _lvLog = listview;
        }

        private ListViewItem makeLvi(string text)
        {
            ListViewItem ret = new ListViewItem { Text = DateTime.Now.ToString() };
            ret.SubItems.Add(text);
            return ret;
        }
    }
}

【问题讨论】:

  • 那个方法看起来像是listviewlogger的构造函数
  • @miparnisari 我考虑过从构造函数接受列表视图引用,但我不能,因为 DI,我没有列表视图的范围。
  • 应该存在接口以支持客户要求。 客户端什么时候需要调用SetListViewReference
  • @MarkSeemann 我的 ListViewLogger 类如何在没有引用 ListView 的情况下记录我的应用程序中发生的事件?
  • ...让它自己创建它,或者通过它的构造函数注入它...同时,请考虑一下:*.com/a/7906547/126014

标签: c# dependency-injection interface


【解决方案1】:

有一个新的IListViewLogger 接口,该接口将从ILogger 继承并具有该附加方法:

public interface ILogger
{
    void Log(string text);
}

public interface IListViewLogger : ILogger
{
    void SetListViewReference(ListView listview);
}

public class ListViewLogger : IListViewLogger
{
    //...
}

不过,设计实际上取决于 ListView 的范围。

如果应用程序中只有一个 ListView,那么它应该设置在组合根中的某个位置,因为您知道记录器的具体类型,所以您可以在其中设置它。如果范围是按视图的,那么拥有这个扩展接口就很好了,因为您需要以某种方式告诉记录器它应该记录到哪里。

【讨论】:

  • 哈哈!这正是我在问这个问题之前的想法,但这会破坏 DI 的全部目的,因为并非所有 Logger 都会实现 IListViewLogger。我的 Form1 ctor 需要接受所有类型的实现 ILogger 的记录器,所以我不能让我的 Form1 ctor 接受 IListViewLogger。
【解决方案2】:

如果您想从ILogger 界面移除对 UI 控件的依赖,您可以应用dependency inversion principle。一种方法是在界面中添加event。为简单起见,我不会介绍另一个接口,即:

interface ILogger
{
    void Log(string msg);
    event Action<string> LogMessages;
}

class PublisherLogger : ILogger
{
    public event Action<string> LogMessages = (msg) => { };

    public void Log(string msg)
    {
        Console.WriteLine(msg);
        LogMessages(msg);
    }
}

然后在 Form 控件中使用它:

public Form1()         
{        
    InitializeComponent(); 
    var publisherLogger = new PublisherLogger(); //or inject it
    publisherLogger.LogMessages += msg =>
    {
        //add msg to list view
        ListViewItem item = new ListViewItem { Text = DateTime.Now.ToString()     };
        item.SubItems.Add(text);
        _listView.Items.Add(ret);
    };
}

您还可以将附加 ListViewLogMessagesetup 代码提取到扩展方法中,以使事情更加干燥。

旁注:DIP is not the same as DI

【讨论】:

  • 如果我要登录到控制台,我不会在这里寻求帮助,因为它会那么简单,但我想登录到 ListView 并且 ListView 是 Form1 的一部分。也许您可以修改您的解决方案以支持外部 ListView。
  • @user1803300 我已经更新了代码示例。如果它仍然不符合您的期望,请使用您希望如何在自定义Form 中使用记录器的示例相应地更新问题。
  • 我看到了您的编辑,看起来您正在从 Form1 类登录,我不希望它看起来像那样,我想要一次调用 ListViewLogger.Log() 并让ListViewLogger 为我处理所有这些。
  • @user1803300 现在我更困惑你想要什么。 ListViewLogger.Log() 应该做什么?
  • 我刚刚说明了我想为我处理所有逻辑的方法,显然它会(在 Form1 内部)被称为 _lvLogger.Log()。 (其中 _lvLogger 是声明为 ILogger 的私有只读字段)