Я уточнил свой предыдущий ответ, но так как он несколько отличается от предложенного мной другого подхода, я подумал, что могу просто сделать другой ответ. Сначала нам нужно объявить несколько интерфейсов:
// 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 ().
Я думаю, что этот набор классов мог бы послужить хорошей основой для всей вашей архитектуры, с дополнительным преимуществом в том, что он не использует ни одного синглтона (всегда передавая ссылки на соответствующие объекты). Это также означает, что реализация фиктивных классов для модульных тестов будет довольно простой.