Язык Dart. Требуется реализовать методы retrieveItems и getItemFromCache менеджера кеширования — класс ObjectDdCacheManager.
Условия:
1. Учитывать FetchDataPolicy — если never, то запрос к API не выполняем, возвращаем то что есть в кеше (или ничего). Если onEmptyCache — то делаем запрос к API только в случае, если в кеше нет данных. Если always — то всегда делаем запрос к API и обновляем данные кеша.
2. Учитывать CacheUpdateMode. Если entities — то только заменяем сущности. Если box — то удаляем старую коробку целиком со всеми объектами.
3. Учитывать фильтр Filter. Если установлен, то запрос к базе делаем через getBeyKey (вид фильтра entityKey) или select (вид range). Фильтр применяем и для удаления сущностей в режиме entities — удаляем все сущности из коробки, которые соответствуют фильтру (а если фильтр не установлен — то просто заменяем сущности методом putAll).
4. Учитывать флаг useMemoryCache. Если установлен, то кроме базы данных используем дополнительно и кеш в памяти — _memoryCache. С ним работаем так же как с базой (можно добавить обертку по аналогии с моим моком ObjectDdMock). Если данные есть в _memoryCache и не нужно обновлять данные — в базу не лезем.
Для удобства, чтобы код собирался, добавлен раздел с моками — его трогать не нужно.
// ****************************** MOCKS ******************************
class ErrorInfo {
final int errorCode;
ErrorInfo(this.errorCode);
}
class ResultOrError<TResult> {
late bool isSuccess;
TResult? result;
ErrorInfo? error;
ResultOrError._fromResult(this.result) {
isSuccess = true;
error = null;
}
ResultOrError._fromError(this.error) {
isSuccess = false;
result = null;
}
factory ResultOrError.fromResult(TResult result) =>
ResultOrError._fromResult(result);
factory ResultOrError.fromError(ErrorInfo error) =>
ResultOrError._fromError(error);
}
class ApiObject1 {
final Map<String, String> items = {"item1": "value1", "item2": "value2"};
}
abstract class DataEntity {
String get key;
}
class DataEntity1 extends DataEntity {
String name;
String value;
@override
String get key => name;
DataEntity1(this.name, this.value);
}
class Model1 {
final String item;
Model1(this.item);
}
class ObjectDdMock<TDataEntity extends DataEntity> {
String get boxKey => TDataEntity.runtimeType.toString();
static final Map<String, Map<String, dynamic>> _boxes = {};
List<TDataEntity> getAll() {
return _boxes[boxKey]?.values.cast<TDataEntity>().toList() ?? [];
}
TDataEntity? getBeyKey(String key) {
final box = _boxes[boxKey];
if (null == box) return null;
return box[key] as TDataEntity;
}
List<TDataEntity> select(String startKey, String endKey) {
final box = _boxes[boxKey];
if (null == box) return [];
final allEntities = getAll();
List<String> keys = allEntities
.map((e) => e.key)
.where((k) => k.compareTo(startKey) >= 0 && k.compareTo(endKey) <= 0)
.toList();
keys.sort();
List<TDataEntity> result = [];
for (final key in keys) {
result.add(box[key]!);
}
return result;
}
void putAll(List<TDataEntity> entities) {
for (final entity in entities) {
put(entity);
}
}
void put(TDataEntity entity) {
if (!_boxes.containsKey(boxKey)) {
_boxes[boxKey] = {entity.key: entity};
} else {
_boxes[boxKey]![entity.key] = entity;
}
}
void deleteBox() {
if (_boxes.containsKey(boxKey)) {
_boxes.remove(boxKey);
}
}
void deleteEntities(List<TDataEntity> entities) {
final box = _boxes[boxKey];
if (null != box) {
for (final entity in entities) {
box.remove(entity.key);
}
}
}
}
// ****************************** END MOCKS ******************************
enum FetchDataPolicy { never, onEmptyCache, always }
enum CacheUpdateMode { entities, box }
enum FilterKind { entityKey, range }
abstract class Filter {
FilterKind get kind;
}
class FilterByKey implements Filter {
String key;
@override
FilterKind get kind => FilterKind.entityKey;
FilterByKey(this.key);
}
class FilterByRange implements Filter {
String startKey;
String endKey;
@override
FilterKind get kind => FilterKind.range;
FilterByRange({required this.startKey, required this.endKey});
}
abstract class CacheManager<TApiObject, TDataEntity extends DataEntity,
TModel> {
final Future<ResultOrError<TApiObject>> Function() fetchFromApi;
final List<TDataEntity> Function(TApiObject) transformToDataEntities;
final TModel Function(TDataEntity) transformToModel;
final String boxName;
final bool useMemoryCache;
CacheManager(
{required this.fetchFromApi,
required this.transformToDataEntities,
required this.transformToModel,
required this.boxName,
this.useMemoryCache = false});
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter});
Future<TModel?> getItemFromCache(String key);
}
final Map<String, Map<String, dynamic>> _memoryCache = {};
class ObjectDdCacheManager<TApiObject, TDataEntity extends DataEntity, TModel>
extends CacheManager<TApiObject, TDataEntity, TModel> {
ObjectDdCacheManager(
{required super.fetchFromApi,
required super.transformToDataEntities,
required super.transformToModel,
required super.boxName});
@override
Future<ResultOrError<List<TModel>>> retrieveItems(
{FetchDataPolicy fetchDataPolicy = FetchDataPolicy.onEmptyCache,
CacheUpdateMode cacheUpdateMode = CacheUpdateMode.entities,
Filter? filter}) {
// TODO: implement retrieveItems
throw UnimplementedError();
}
@override
Future<TModel?> getItemFromCache(String key) {
// TODO: implement getItemFromCache
throw UnimplementedError();
}
}
void main() async {
final cacheManager = ObjectDdCacheManager(
fetchFromApi: () async {
return ResultOrError.fromResult(ApiObject1());
},
transformToDataEntities: (apiObject) {
return apiObject.items.keys
.map((k) => DataEntity1(k, apiObject.items[k]!))
.toList();
},
transformToModel: (dataEntity) {
return Model1(dataEntity.value);
},
boxName: 'box1');
final items = await cacheManager.retrieveItems();
print(items.result!.length);
}