patterncsharpMinor
Maintaining a cache structure with elements
Viewed 0 times
elementswithcachestructuremaintaining
Problem
I have some code which is designed for the following purposes:
This works, but seems unnecessarily complicated. I essentially have 5 classes and 1 interface to pull this off, and having to declare
Is there a simpler way to implement this?
```
internal interface ICache : IEnumerable
{
bool Contains(object element);
}
internal class DynamicCache : DynamicCache, TElement> {
internal DynamicCache(
Func> initElements
, Func> addElements
, Predicate dropElementsIf
)
: base(initElements, addElements, dropElementsIf)
{
}
}
internal class DynamicCache : Cache where TEnumerable : List {
protected Func AddElements;
protected Predicate DropElementsIf;
internal DynamicCache(
Func initElements
, Func addElements
, Predicate dropElementsIf
)
: base(initElements)
{
this.AddElements = addElements;
this.DropElementsIf = dropElementsIf;
}
protected override sealed void UpdateElements() {
if (!isInitialized) {
this.elements = InitElements();
this.isInitialized = true;
}
else {
this.elements.AddRange(AddElements());
this.elements.RemoveAll(DropElementsIf);
}
}
}
internal class Cache : Cache, TElement> {
internal Cache(Func> initElements)
: base(initElements)
{
- Implement a cache structure which stores cached elements, as well as a method for reloading those elements.
- Ensure the new structure can use any type of
IEnumerableto store the elements.
- Implement a dynamic cache which inherits from the standard cache, as well as storing additional methods for appending new elements and determining which elements to drop.
- Ensure the dynamic cache can only use List to store the cached elements, in order to make use of
List.AddRangeandList.RemoveAll.
This works, but seems unnecessarily complicated. I essentially have 5 classes and 1 interface to pull this off, and having to declare
TEnumerable as IEnumerable in the where clause of a class declaration feels odd at best.Is there a simpler way to implement this?
```
internal interface ICache : IEnumerable
{
bool Contains(object element);
}
internal class DynamicCache : DynamicCache, TElement> {
internal DynamicCache(
Func> initElements
, Func> addElements
, Predicate dropElementsIf
)
: base(initElements, addElements, dropElementsIf)
{
}
}
internal class DynamicCache : Cache where TEnumerable : List {
protected Func AddElements;
protected Predicate DropElementsIf;
internal DynamicCache(
Func initElements
, Func addElements
, Predicate dropElementsIf
)
: base(initElements)
{
this.AddElements = addElements;
this.DropElementsIf = dropElementsIf;
}
protected override sealed void UpdateElements() {
if (!isInitialized) {
this.elements = InitElements();
this.isInitialized = true;
}
else {
this.elements.AddRange(AddElements());
this.elements.RemoveAll(DropElementsIf);
}
}
}
internal class Cache : Cache, TElement> {
internal Cache(Func> initElements)
: base(initElements)
{
Solution
Interface design is wanting.
This looks like this was made by mistake:
It should implement
This way you can do
More importantly though your
Before writing some component, you should be able to write interfaces thereof, and test the required functionality against those interfaces.
In general:
The only way to access the elements in your cache is through enumerating them. Which precludes random access. For example you cannot get a
and this ruins the other two properties.
You cannot exploit there being many more reads than updates, because every read is an update.
You cannot exploit time locality because even if you are reading the same elements over and over again you are updating the collection.
Consider this:
In order to exploit time (or other) locality is choosing the correct caching strategy.
For example if some data feed is updated on average once a day, you might decide to not hit the source if cache contains a data read in the past 6 hours.
Or you might want to have a fixed size cache which automatically evicts the least recently used when it is full and a value not contained therein is accessed.
But these types of common strategies cannot be implemented with a
Some other problems not specific to caches:
and also everytime
-
Every overriding method of
This looks like this was made by mistake:
internal interface ICache : IEnumerable
{
bool Contains(object element);
}It should implement
IEnumerable which already provides IEnumerable functionality. internal interface ICache : IEnumerable
{
bool Contains(TElement element);
}This way you can do
Cache : ICache instead of class Cache : IEnumerable, ICache. Which allows non-type-safe and redundant Contains(object) method.More importantly though your
ICache provides no additional functionality w.r.t any other IEnumerable, given IEnumerable already has a Contains extension method.Before writing some component, you should be able to write interfaces thereof, and test the required functionality against those interfaces.
In general:
- a cache is a specialized Key-Value store.
- They are used to improve read performance where reads outnumber updates by far, and write are relatively expensive.
- They exploit time locality (and sometimes space) locality.
The only way to access the elements in your cache is through enumerating them. Which precludes random access. For example you cannot get a
Wrapper with a given id from the cache.GetEnumerator modifies the underlying collection by calling UpdateElements();, and this ruins the other two properties.
You cannot exploit there being many more reads than updates, because every read is an update.
You cannot exploit time locality because even if you are reading the same elements over and over again you are updating the collection.
Consider this:
DynamicCache wDynamicCache = //......
var wrapper = new Wrapper{id=10, name = "Wrapper 10"};
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False
Console.WriteLine(wDynamicCache.Contains(wrapper)); // True
Console.WriteLine(wDynamicCache.Contains(wrapper)); // FalseIn order to exploit time (or other) locality is choosing the correct caching strategy.
For example if some data feed is updated on average once a day, you might decide to not hit the source if cache contains a data read in the past 6 hours.
Or you might want to have a fixed size cache which automatically evicts the least recently used when it is full and a value not contained therein is accessed.
But these types of common strategies cannot be implemented with a
Predicate dropElementsIf. You might try to wrap the data with last access time etc details, as you already did in your test; but this would leak to the interface, as TElement is now a wrapper with implementation details. For example changing caching strategy will require recompiling the code using the cache.Some other problems not specific to caches:
- everytime
Cacheclasses are accessedUpdateElementsis called even thoughCacheclasses are not dynamic by default.
protected bool isInitialized;is present on Cache
and also everytime
Cache classes are accessed checked even if it consists of a fixed collection of elements.-
Every overriding method of
UpdateElements must start with copied : if (!isInitialized) {
this.elements = InitElements();
this.isInitialized = true;
}Code Snippets
internal interface ICache : IEnumerable
{
bool Contains(object element);
}internal interface ICache<TElement> : IEnumerable<TElement>
{
bool Contains(TElement element);
}DynamicCache<Wrapper> wDynamicCache = //......
var wrapper = new Wrapper{id=10, name = "Wrapper 10"};
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False
Console.WriteLine(wDynamicCache.Contains(wrapper)); // True
Console.WriteLine(wDynamicCache.Contains(wrapper)); // Falseif (!isInitialized) {
this.elements = InitElements();
this.isInitialized = true;
}Context
StackExchange Code Review Q#46409, answer score: 5
Revisions (0)
No revisions yet.