我将以帖子的长度道歉来开始这个问题。所以我节省了一些时间,我的问题是,我被困在脑子里的班级模式显然是有缺陷的,我看不出一个好的解决方案。
在我正在开发的项目中,我需要对一大块数据使用操作算法,让我们称之为DataCache
。有时这些算法会返回自身需要缓存的结果,因此我设计了一个方案。
我有一个看起来像这样的算法基类
abstract class Algorithm<T>
{
protected abstract T ExecuteAlgorithmLogic(DataCache dataCache);
private readonly Dictionary<DataCache, WeakReference> _resultsWeak = new Dictionary<DataCache, WeakReference>();
private readonly Dictionary<DataCache, T> _resultsStrong = new Dictionary<DataCache, T>();
public T ComputeResult(DataCache dataCache, bool save = false)
{
if (_resultsStrong.ContainsKey(dataCache))
return _resultsStrong[dataCache];
if (_resultsWeak.ContainsKey(dataCache))
{
var temp = _resultsWeak[dataCache].Target;
if (temp != null) return (T) temp;
}
var result = ExecuteAlgorithmLogic(dataCache);
_resultsWeak[dataCache] = new WeakReference(result, true);
if (save) _resultsStrong[dataCache] = result;
return result;
}
}
如果您致电ComputeResult()
并提供DataCache
,您可以选择缓存结果。此外,如果您幸运的话,如果GC还没有收集它,那么结果仍然存在。每个DataCache的大小都在几百兆字节之内,在你问之前每个大约有10个数组,它们包含int
和float
等基本类型。
我的想法是,实际算法看起来像这样:
class ActualAgorithm : Algorithm<SomeType>
{
protected override SomeType ExecuteAlgorithmLogic(DataCache dataCache)
{
//Elves be here
}
}
我会定义几十个.cs文件,每个文件用于一个算法。这种方法存在两个问题。首先,为了使其工作,我需要实例化我的算法并保留该实例(或者结果不被缓存而整个点都是静音的)。但后来我在每个派生类中都得到了一个难看的单例模式实现。它看起来像这样:
class ActualAgorithm : Algorithm<SomeType>
{
protected override SomeType ExecuteAlgorithmLogic(DataCache dataCache)
{
//Elves and dragons be here
}
protected ActualAgorithm(){ }
private static ActualAgorithm _instance;
public static ActualAgorithm Instance
{
get
{
_instance = _instance ?? new ActualAgorithm();
return _instance;
}
}
}
因此,在每个实现中,我都必须复制单例模式的代码。其次,数十个CS文件听起来有点矫枉过正,因为我真正想要的只是一个函数,它返回一些可以为各种DataCache
对象缓存的结果。当然必须有一个更聪明的方法来做到这一点,我非常感谢在正确的方向上轻推。
答案 0 :(得分:2)
您可以让您的算法独立于其结果:
class Engine<T> {
Map<AlgorithmKey, Algorithm<T>> algorithms;
Map<AlgorithmKey, Data> algorithmsResultCache;
T processData(Data in);
}
interface Algorithm<T> {
boolean doesResultNeedsToBeCached();
T processData(Data in);
}
然后,您的Engine负责实现算法,这些算法只是输入数据且输出为空或某些数据的代码片段。每个算法都可以说明他的结果是否需要缓存。
为了改进我的答案,我认为你应该对如何运行算法给出一些准确性(是否有订单,用户是否可调,我们事先知道将要运行的算法,... )。
答案 1 :(得分:2)
我对评论的意思是这样的:
abstract class BaseClass<K,T> where T : BaseClass<K,T>, new()
{
private static T _instance;
public static T Instance
{
get
{
_instance = _instance ?? new T();
return _instance;
}
}
}
class ActualClass : BaseClass<int, ActualClass>
{
public ActualClass() {}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ActualClass.Instance.GetType().ToString());
Console.ReadLine();
}
}
这里唯一的问题是你将拥有一个公共构造函数。
答案 2 :(得分:2)
我改进了我以前的答案,但因为它与我提出的其他方法有很大的不同,我想我可能会再做一个答案。首先,我们需要声明一些接口:
// Where to find cached data
interface DataRepository {
void cacheData(Key k, Data d);
Data retrieveData(Key k, Data d);
};
// If by any chance we need an algorithm somewhere
interface AlgorithmRepository {
Algorithm getAlgorithm(Key k);
}
// The algorithm that process data
interface Algorithm {
void processData(Data in, Data out);
}
鉴于这些接口,我们可以为算法存储库定义一些基本实现:
class BaseAlgorithmRepository {
// The algorithm dictionnary
Map<Key, Algorithm> algorithms;
// On init, we'll build our repository using this function
void setAlgorithmForKey(Key k, Algorithm a) {
algorithms.put(k, a);
}
// ... implement the other function of the interface
}
然后我们也可以为DataRepository实现一些东西
class DataRepository {
AlgorithmRepository algorithmRepository;
Map<Key, Data> cache;
void cacheData(Key k, Data d) {
cache.put(k, d);
}
Data retrieveData(Key k, Data in) {
Data d = cache.get(k);
if (d==null) {
// Data not found in the cache, then we try to produce it ourself
Data d = new Data();
Algorithm a = algorithmRepository.getAlgorithm(k);
a.processData(in, d);
// This is optional, you could simply throw an exception to say that the
// data has not been cached and thus, the algorithm succession did not
// produce the necessary data. So instead of the above, you could simply:
// throw new DataNotCached(k);
// and thus halt the whole processing
}
return d;
}
}
最后,我们开始实现算法:
abstract class BaseAlgorithm {
DataRepository repository;
}
class SampleNoCacheAlgorithm extends BaseAlgorithm {
void processData(Data in, Data out) {
// do something with in to compute out
}
}
class SampleCacheProducerAlgorithm extends BaseAlgorithm {
static Key KEY = "SampleCacheProducerAlgorithm.myKey";
void processData(Data in, Data out) {
// do something with in to compute out
// then call repository.cacheData(KEY, out);
}
}
class SampleCacheConsumerAlgorithm extends BaseAlgorithm {
void processData(Data in, Data out) {
// Data tmp = repository.retrieveData(SampleCacheProducerAlgorithm.KEY, in);
// do something with in and tmp to compute out
}
}
基于此,我认为您还可以定义一些特殊类型的算法,这些算法实际上只是其他算法的组合,但也实现了算法接口。一个例子可能是:
class AlgorithmChain extends BaseAlgorithm {
List<Algorithms> chain;
void processData(Data in, Data out) {
Data currentIn = in;
foreach (Algorithm a : chain) {
Data currentOut = new Data();
a.processData(currentIn, currentOut);
currentIn = currentOut;
}
out = currentOut;
}
}
我要对此做的一个补充是DataPool,它允许您重用现有但未使用的Data对象,以避免每次创建新Data()时分配大量内存。
我认为这组类可以为整个架构提供良好的基础,还有一个额外的好处就是它不使用任何Singleton(总是传递对相关对象的引用)。这也意味着为单元测试实现虚拟类很容易。
答案 3 :(得分:1)
您是否可以使用算法的组合存储库/工厂注册您的算法实例,这些算法将保留对它们的引用?存储库可以是单例,如果您为存储库提供算法实例化控制,则可以使用它来确保每个存在只有一个实例。
public class AlgorithmRepository
{
//... use boilerplate singleton code
public void CreateAlgorithm(Algorithms algorithm)
{
//... add to some internal hash or map, checking that it hasn't been created already
//... Algorithms is just an enum telling it which to create (clunky factory
// implementation)
}
public void ComputeResult(Algorithms algorithm, DataCache datacache)
{
// Can lazy load algoirthms here and make CreateAlgorithm private ..
CreateAlgorithm(algorithm);
//... compute and return.
}
}
这就是说,对每个算法都有一个单独的类(和cs文件)对我有意义。你可以打破约定,并且如果它们是轻量级的,那么在单个cs文件中有多个algo类,如果你担心文件的数量,它会更容易管理 - 有更糟糕的事情去做。 FWIW我只是忍受了文件的数量......
答案 4 :(得分:1)
首先,我建议您将DataCache重命名为DataInput,以便更清晰,因为很容易将其与真正充当缓存的对象(_resultsWeak和_resultsStrong)混淆以存储结果。
考虑到需要将这些缓存留在内存中以备将来使用,您可以考虑将它们放在.NET应用程序中存在的范围之一,而不是对象范围,例如Application或Session。
您还可以使用AlgorithmLocator(请参阅ServiceLocator模式)作为所有算法的单一访问点,以消除每个算法中的单例逻辑重复。
除此之外,我发现你的解决方案在全球范围内都是一个不错的选择。是否过度杀伤将基本上取决于算法的同质性。如果他们都有相同的缓存数据的方式,返回他们的结果......将所有逻辑分解在一个地方将是一个很大的好处。但我们缺乏判断的背景。
将缓存逻辑封装在算法(CachingStrategy?)所持有的特定对象中也可以替代继承它,但可能有点尴尬,因为缓存对象必须在计算之前和之后访问缓存并且需要能够自己触发算法计算并掌握结果。
[编辑]如果你担心每个算法都有一个.cs文件,你总是可以将属于特定T的所有算法类分组到同一个文件中。
答案 5 :(得分:1)
通常在创建Singleton类时,您不希望从中继承它。当你这样做时,你会失去一些单身人士模式的善良(我从模式狂热者那里听到的是,每当你做这样的事情时,天使会失去翅膀)。但是要务实......有时候你会做你必须做的事情。
无论如何,我认为无论如何组合泛型和继承都不会起作用。
你指出算法的数量将是十(而不是几百)。只要是这种情况,我就会创建一个键入System.Type的字典,并将对方法的引用存储为字典的值。在这种情况下,我用过
Func<DataCache, object>
作为字典值签名。
当类首次实例化时,在字典中注册所有可用的算法。在运行时,当类需要为类型T执行算法时,它将获得T的类型并在字典中查找算法。
如果算法的代码相对复杂,我建议将它们拆分为部分类,以保证代码的可读性。
public sealed partial class Algorithm<T>
{
private static object ExecuteForSomeType(DataCache dataCache)
{
return new SomeType();
}
}
public sealed partial class Algorithm<T>
{
private static object ExecuteForSomeOtherType(DataCache dataCache)
{
return new SomeOtherType();
}
}
public sealed partial class Algorithm<T>
{
private readonly Dictionary<System.Type, Func<DataCache, object>> _algorithms = new Dictionary<System.Type, Func<DataCache, object>>();
private readonly Dictionary<DataCache, WeakReference> _resultsWeak = new Dictionary<DataCache, WeakReference>();
private readonly Dictionary<DataCache, T> _resultsStrong = new Dictionary<DataCache, T>();
private Algorithm() { }
private static Algorithm<T> _instance;
public static Algorithm<T> Instance
{
get
{
if (_instance == null)
{
_instance = new Algorithm<T>();
_instance._algorithms.Add(typeof(SomeType), ExecuteForSomeType);
_instance._algorithms.Add(typeof(SomeOtherType), ExecuteForSomeOtherType);
}
return _instance;
}
}
public T ComputeResult(DataCache dataCache, bool save = false)
{
T returnValue = (T)(new object());
if (_resultsStrong.ContainsKey(dataCache))
{
returnValue = _resultsStrong[dataCache];
return returnValue;
}
if (_resultsWeak.ContainsKey(dataCache))
{
returnValue = (T)_resultsWeak[dataCache].Target;
if (returnValue != null) return returnValue;
}
returnValue = (T)_algorithms[returnValue.GetType()](dataCache);
_resultsWeak[dataCache] = new WeakReference(returnValue, true);
if (save) _resultsStrong[dataCache] = returnValue;
return returnValue;
}
}