【问题标题】:Encapsulating an expensive resource without using a Singleton在不使用单例的情况下封装昂贵的资源
【发布时间】:2011-03-15 12:59:47
【问题描述】:

我正在更新一个绝对充斥着 Singleton 类的遗留应用程序。一个完美的例子是 SnmpConnector 类:

public SnmpConnector
{
  public static IEnumerable<string> HostIpAddresses
  {
    ...
  }

  private static SnmpConnector instance;
  public static SnmpConnector Instance
  {
    if (instance == null)
      instance = new SnmpConnector();
    return instance;
  }

  private SnmpConnector()
  {
    foreach (string IpAddress in HostIpAddresses)
    {
      ...
    }
  }

  ...
}

此更新的目标是提高代码库的可测试性,因此我想摆脱单例。我已经抽象出 SnmpConnector 的数据源,以便从测试数据库或查询实时服务器获取数据:

public interface ISnmpDataSource
{
  public DataTable MacTable
  {
    get;
    private set;
  }

  public DataTable PortTable
  {
    get;
    private set;
  }

  ...
}

public TestSnmpDataSource : ISnmpDataSource
{
  public FileInfo DataSource
  {
    get;
    private set;
  }

  ...
}

public SnmpDataSource : ISnmpDataSource
{
  public List<string> HostIpAddresses
  {
    get;
    private set;
  }

  ...
}

public SnmpConnector
{
  public SnmpConnector(ISnmpDataSource DataSource)
  {
    ...
  }

  ...
}

现在,我正在尝试测试这些组件并遇到了可能导致 SnmpConnector 成为单例的问题:测试 SnmpDataSource 需要花费大量时间。事实证明,从实时交换机获取 MAC 表和端口表需要 10 到 20 秒。我已经为这个特定的类编写了 13 个单元测试,因此完成这些测试需要两分钟多的时间。尽管这很烦人,但一旦这些更新发布到我们的原始代码库,情况就会变得更糟。通过这种新的重构,没有什么能阻止程序员反复创建和丢弃 SnmpDataSource。

现在,这些表中的数据大部分是静态的;旧的 Singleton 和新的 SnmpDataSource 都维护一个仅每四个小时更新一次的缓存。我是否必须将 SnmpDataSource 设为 Singleton 才能防止出现此问题?

【问题讨论】:

    标签: c# caching singleton


    【解决方案1】:

    使用依赖注入,或者将SnmpDataSource 传递给任何需要它的东西,或者可能传入一个Func&lt;SnmpDataSource&gt;,它可以根据需要懒惰地创建实例。

    您的目标是SnmpDataSource 应该自行更新,还是调用者会在几个小时后获得新版本?

    【讨论】:

    • SnmpDataSource 只知道如何通过与开关通信来返回几个表。然后 SnmpConnector 缓存这些数据并继续其愉快的方式。但是,SnmpDataSource 需要 10-20 秒来获取数据。我已经将 SnmpDataSource 注入到 SnmpConnector 中,但是可以为 SnmpConnector 的两个不同实例创建两个不同的 SnmpDataSource,这会导致 20-40 秒的延迟。
    • 应该添加,我的目标是如果任何实例在过去 4 小时内已经完成了数据,我的目标是不让任何 SnmpDataSource 实例出去获取数据。
    • @DonaldRay:你可以传入一个缓存的Func&lt;SnmpDataSource&gt;...它基本上就像一个单例,但以可注入(和可测试)的方式。
    【解决方案2】:

    您可以尝试使用实现相同接口的缓存感知版本包装/装饰SnmpDataSource,然后注入缓存感知版本。

    *edit -- 或者你可以按照 Jon 建议的方法来代替工厂 Func 进行缓存(它将返回一个新实例或缓存版本,具体取决于创建最后一个实例的时间)。同样的事情,实现方式略有不同。 Jon 的版本可能更有意义。

    public CachedSnmpDataSource : ISnmpDataSource
    {
      private DateTime m_lastRetrieved;
      private TimeSpan m_cacheExpiryPeriod;
      private List<string> m_hostIpAddresses;
      private Func<SnmpDataSource> m_dataSourceCreator
    
      public CachedSnmpDataSource(Func<SnmpDataSource> dataSourceCreator, TimeSpan cacheExpiryPeriod)
      {
          m_dataSourceCreator = dataSourceCreator;
          m_cacheExpiryPeriod = cacheExpiryPeriod;
      }
    
      public List<string> HostIpAddresses
      {
        get
        {
           if(!IsRecentCachedVersionAvailable()) 
           {
               CreateCachedVersion();
           }
    
           return new List<string>(m_hostIpAddresses);
        } 
    
        private bool IsRecentCachedVersionAvailable()
        {
            return m_hostIpAddresses != null && 
                   (DateTime.Now - m_lastRetrieved) < m_cacheExpiryPeriod;
        }
    
        private void CreateCachedVersion()
        {  
           SnmpDataSource dataSource = m_dataSourceCreator();
           m_hostIpAddresses = dataSource.HostIpAddresses;
           m_lastRetrieved = DateTime.Now;       
        }
      }
    
      ...
    }
    

    【讨论】:

      【解决方案3】:

      经过几次迭代,我最终找到了一个巧妙的解决方案来解决这个问题。我将保留已接受的答案,但这是我最终使用的:

      1. ISnmpDataSource 和以前一样负责获取数据。
      2. SnmpConnector 知道只查询自己的缓存是否无效。
      3. 静态工厂类维护Dictionary&lt;ISnmpDataSource, SnmpConnector&gt;。有一个基于此字典的static BuildSnmpConnector(ISnmpDataSource) 方法。

      现在使用库看起来像这样:

      IEnumerable<string> IpAddresses = ...;
      string SqlConString = @"...";
      ISnmpDataSource Switches = new SnmpDataSource(IpAddresses, SqlConStr);
      SnmpConnector instance = Factory. BuildSnmpConnector(Switches);
      

      我在如何为 ISnmpDataSource 实现中实现 GetHashCodeEquals 时遇到了一些问题,但是将相等的定义形式化几乎解决了所有这些问题。

      我对最终结果很满意; Factory 类负责限制实例化,SnmpConnector 负责缓存查询结果,而ISnmpDataSource 负责实际运行查询。我敢肯定那里有更好的组织,但这个组织足够干净,可以使用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-23
        • 2020-03-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多