Implement cache size limitation and eviction for CachedWeatherForecaster.

Co-Authored-By: os222
This commit is contained in:
Gleb Koval 2024-11-19 09:14:09 +00:00
parent 01f8d801b3
commit 30cd615adb
Signed by: cyclane
GPG Key ID: 15E168A8B332382C
3 changed files with 52 additions and 3 deletions

View File

@ -1,14 +1,19 @@
package ic.doc; package ic.doc;
import java.util.ArrayDeque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Queue;
// This class is a decorator for WeatherForecaster that caches all results. // This class is a decorator for WeatherForecaster that caches all results.
public class CachedWeatherForecaster implements WeatherForecaster { public class CachedWeatherForecaster implements WeatherForecaster {
private final WeatherForecaster forecaster; private final WeatherForecaster forecaster;
private final Integer maxCacheSize;
private final HashMap<CacheKey, WeatherForecast> cache = new HashMap<>(); private final HashMap<CacheKey, WeatherForecast> cache = new HashMap<>();
private final Queue<CacheKey> evictionQueue = new ArrayDeque<>();
CachedWeatherForecaster(WeatherForecaster forecaster) { CachedWeatherForecaster(WeatherForecaster forecaster, Integer maxCacheSize) {
this.forecaster = forecaster; this.forecaster = forecaster;
this.maxCacheSize = maxCacheSize;
} }
@Override @Override
@ -17,11 +22,19 @@ public class CachedWeatherForecaster implements WeatherForecaster {
WeatherForecast forecast = cache.get(key); WeatherForecast forecast = cache.get(key);
if (forecast == null) { if (forecast == null) {
forecast = forecaster.forecastFor(region, day); forecast = forecaster.forecastFor(region, day);
cache.put(key, forecast); putCache(key, forecast);
return forecast; return forecast;
} }
return forecast; return forecast;
} }
private void putCache(CacheKey key, WeatherForecast forecast) {
if (maxCacheSize != null && cache.size() >= maxCacheSize) {
cache.remove(evictionQueue.poll());
}
cache.put(key, forecast);
evictionQueue.add(key);
}
private record CacheKey(WeatherRegion region, Weekday day) {} private record CacheKey(WeatherRegion region, Weekday day) {}
} }

View File

@ -2,6 +2,7 @@ package ic.doc;
public class CachedWeatherForecasterBuilder { public class CachedWeatherForecasterBuilder {
private WeatherForecaster forecaster; private WeatherForecaster forecaster;
private Integer maxCacheSize = null;
private CachedWeatherForecasterBuilder() {} private CachedWeatherForecasterBuilder() {}
@ -14,7 +15,12 @@ public class CachedWeatherForecasterBuilder {
return this; return this;
} }
public CachedWeatherForecasterBuilder withMaxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
return this;
}
public CachedWeatherForecaster build() { public CachedWeatherForecaster build() {
return new CachedWeatherForecaster(forecaster); return new CachedWeatherForecaster(forecaster, maxCacheSize);
} }
} }

View File

@ -55,4 +55,34 @@ public class CachedWeatherForecasterTest {
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY); cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
assertEquals(expForecast, actForecast); assertEquals(expForecast, actForecast);
} }
@Test
public void forecastForCacheEvictedCall() {
WeatherForecaster cachedForecaster = cachedWeatherForecaster.withMaxCacheSize(2).build();
WeatherForecast expForecast = new WeatherForecast("Sunny Spells", 30);
WeatherForecast badForecast = new WeatherForecast("Rainy", 10);
context.checking(new Expectations() {{
// Two calls since cache gets evicted
exactly(2).of(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
will(returnValue(expForecast));
// Also make some other requests in-between
oneOf(forecaster).forecastFor(WeatherRegion.LONDON, Weekday.TUESDAY);
will(returnValue(badForecast));
oneOf(forecaster).forecastFor(WeatherRegion.WALES, Weekday.MONDAY);
will(returnValue(badForecast));
}});
// Cache put
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
// Cache put
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.TUESDAY);
// Size exceeded, (London, Monday) entry evicted
cachedForecaster.forecastFor(WeatherRegion.WALES, Weekday.MONDAY);
// Size exceeded, (London, Tuesday) entry evicted
WeatherForecast actForecast =
cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
assertEquals(expForecast, actForecast);
// Caching still works (3rd call not made)
actForecast = cachedForecaster.forecastFor(WeatherRegion.LONDON, Weekday.MONDAY);
assertEquals(expForecast, actForecast);
}
} }