我假设一个对象代表某个东西,你只能在内存中有一个实例。我避免重复和等于对象。
假设一个对象由City
类型的“纽约”唯一标识,包含在缓存中(扩展System.Runtime.Caching.ObjectCache
)。此对象由另一个名为MyBusinessObject
缓存删除“纽约”对象,但对象MyBusinessObject
仍然引用“纽约”。垃圾收集器不会从内存中删除此对象,因为它仍然具有引用。
另一个对象从缓存中请求“纽约”。缓存为“纽约”
City
的新实例
醇>
现在有两个“纽约”实例,一个MyBusinessObject
引用的实例(无效)和缓存引用的新“纽约”实例。
是否有设计模式可以解决此问题?我不希望MyBusinessObject
使用City
的陈旧实例。
一种可能的解决方案不会消除被引用的缓存对象,但是,如何做到这一点?
以下是我正在尝试解释的UML图:
答案 0 :(得分:2)
在这种情况下,您的缓存不应该缓存实际的对象,而是缓存正在缓存的对象的包装器,它还包含有关缓存中对象状态的信息。
例如,您可以拥有一个类似的简单类,表示缓存中的项目:
public class CacheItem<T>
{
// Since the cache is the only thing
// that should be making CacheItems,
// make this internal to the assembly
// that the cache is implemented in.
// This constructor is called before
// an add.
internal CacheItem(T item)
{
// Set the property values.
Item = item;
}
// Poor-man's immutability.
public T Item { get; private set; }
// The backing field for IsCached, it
// is volatile so that it can serialize
// access for single reads/writes, which is
// what the property does.
// Assume it is being added when constructed.
private volatile bool _isCached = true;
// Only able to be set by the cache.
// The setter is set to false when the item
// is stale (not in the cache any longer).
public bool IsCached
{
get { return _isCached; }
set { _isCached = value; }
}
}
这里的想法很简单:
当您的缓存即将在缓存中输入项目的新实例时,它会调用构造函数(构造函数应仅可用于缓存,如有必要,您可以使CacheItem
具有私有构造函数而不是内部的嵌套类,它将IsCached
属性值设置为true。然后将该项目放入缓存中。
当项目从缓存过期时,缓存会将IsCached
属性设置为false(同样,此属性只能由缓存访问)。
责任移至客户端以检查CacheItem<T>
是否过时。
请注意,这是一个拉动操作。如果需要,可以通过向CacheItem<T>
添加事件来添加推送操作支持,如下所示:
public class CacheItem<T>
{
// Since the cache is the only thing
// that should be making CacheItems,
// make this internal to the assembly
// that the cache is implemented in.
// This constructor is called before
// an add.
internal CacheItem(T item)
{
// Set the property values.
Item = item;
}
// Poor-man's immutability.
public T Item { get; private set; }
// The lock for the event registrations.
// Since everything else is immutable, this needs
// to be as well.
private readonly object _eventLock = new object();
// The backing field for the Expired
// event. Since everything else is immutable
// this needs to be as well.
private readonly EventHandler _expiredHandlers;
// The expires event.
public event EventHandler Expired
{
add { lock (_eventLock) _expiredHandlers += value; }
remove { lock (_eventLock) _expiredHandlers -= value; }
}
// The backing field for IsCached, it
// is volatile so that it can serialize
// access for single reads/writes, which is
// what the property does.
// Assume it is being added when constructed.
private volatile bool _isCached = true;
// The setter is set to false by the
// Expire method (called by the cached)
// when the item is stale
// (not in the cache any longer).
public bool IsCached { get { return _isCached; } }
// Called internally by the cache.
internal void Expire()
{
// Set _isCached to false.
_isCached = false;
// Get the event handlers and fire
// the event. Getting the handlers
// needs to be synchronized.
EventHandler handlers;
// Synchronize.
lock (_eventLock) handlers = _expiredHandlers;
// Fire if there are handlers.
if (handlers != null) handlers(this, EventArgs.Empty);
}
}
现在,您可以让您的客户订阅Expired
事件,该事件将在缓存项无效时向客户端指示。通过在缓存中删除项目时调用项目上的内部Expire
事件,缓存会触发此操作(并且它还会将IsCached
属性设置为false)。