我正在努力更新一个绝对充满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首先成为Singleton的问题:测试SnmpDataSource需要花费大量时间。事实证明,从实时交换机获取MAC表和端口表需要10到20秒之间的时间。我已经为这个特定的类编写了13个单元测试,因此完成这些测试需要两分钟。虽然这很令人讨厌,但是一旦这些更新发布到我们原始的代码库,它就会变得更糟。通过这种新的重构,没有什么能阻止程序员反复创建和丢弃SnmpDataSource。
现在,这些表中的数据基本上是静态的;旧的Singleton和新的SnmpDataSource都维护一个仅每四个小时更新一次的缓存。我是否必须将SnmpDataSource设为Singleton以防止出现此问题?
答案 0 :(得分:3)
使用依赖注入,并将SnmpDataSource
传递给任何需要它的东西,或者传入Func<SnmpDataSource>
,这可以根据需要懒惰地创建实例。
你的目标是SnmpDataSource
应该自行更新,或者呼叫者会在几小时后获得新版本吗?
答案 1 :(得分:0)
您可以尝试使用实现相同接口的缓存感知版本来包装/装饰SnmpDataSource
,然后注入缓存感知版本。
*编辑 - 或者你可以做乔恩建议工厂Func进行缓存的地方(它将返回一个新实例或缓存版本,具体取决于创建最后一个实例的时间)。同样的事情,实现略有不同。乔恩的版本可能更有意义。
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;
}
}
...
}
答案 2 :(得分:0)
经过几次迭代,我最终找到了解决这个问题的巧妙方法。我将按原样留下接受的答案,但这是我最终使用的:
ISnmpDataSource
负责提取数据,如前所述。 SnmpConnector
知道只询问自己的缓存是否无效。Dictionary<ISnmpDataSource, SnmpConnector>
。基于此字典有一种static BuildSnmpConnector(ISnmpDataSource)
方法。 现在使用该库看起来像这样:
IEnumerable<string> IpAddresses = ...;
string SqlConString = @"...";
ISnmpDataSource Switches = new SnmpDataSource(IpAddresses, SqlConStr);
SnmpConnector instance = Factory. BuildSnmpConnector(Switches);
我在如何为GetHashCode
实现实现Equals
和ISnmpDataSource
方面遇到了一些问题,但正式化相等的定义几乎解决了所有这些问题。
我对最终结果非常满意; Factory
类负责限制实例化,而SnmpConnector
负责缓存查询结果,而ISnmpDataSource
负责实际运行查询。我确信那里有一个更好的组织,但这个组织足够干净,可以使用。