Implement cache size limitation and eviction for CachedWeatherForecaster.
Co-Authored-By: os222
This commit is contained in:
parent
01f8d801b3
commit
30cd615adb
|
@ -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) {}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue