Course content
Adapter: Wrap LegacyWeatherSdk in a WeatherProvider
Medium·Tagsdesign-patternsadapterstructuralrefactoring
Problem Statement
The application uses a `WeatherProvider` interface for forecasting:
```java
interface WeatherProvider {
double getTemperatureCelsius(String city);
String getConditions(String city); // "sunny", "rainy", "cloudy", "snowy"
}
```
A new third-party library, `LegacyWeatherSdk`, is the only weather data source you have access to. Its API does not match — it returns Fahrenheit and integer condition codes:
```java
class LegacyWeatherSdk {
public double readTempF(String location); // returns Fahrenheit
public int getCondCode(String location); // 1=sunny, 2=rainy, 3=cloudy, 4=snowy
}
```
You cannot change the SDK. The application code (`ForecastService`) is written against `WeatherProvider` and must not import `LegacyWeatherSdk`. Implement `LegacyWeatherAdapter` so that `LegacyWeatherSdk` can be plugged into the application as a `WeatherProvider`.
After your refactor, the adapter must satisfy this contract:
```java
class LegacyWeatherAdapter implements WeatherProvider {
public LegacyWeatherAdapter(LegacyWeatherSdk legacy); // composition: hold the SDK, do not extend it
public double getTemperatureCelsius(String city); // (F - 32) * 5 / 9
public String getConditions(String city); // 1→"sunny", 2→"rainy", 3→"cloudy", 4→"snowy", else "unknown"
}
```
The validator runs three checks:
1. *translates_temperature_and_conditions* — given a `LegacyWeatherSdk` returning known Fahrenheit values (32°F, 50°F, 212°F) and codes 1-4, the adapter must return the corresponding Celsius (0°C, 10°C, 100°C) and condition strings ("sunny", "rainy", "cloudy", "snowy").
2. *unknown_code_falls_back* — for an unrecognised code (e.g. 99), `getConditions(...)` must return `"unknown"`. Throwing or returning `null` is not acceptable; the adapter is the place where unknown legacy codes get handled.
3. *composition_and_end_to_end* — the adapter implements `WeatherProvider`; its constructor accepts a `LegacyWeatherSdk`; it does NOT extend `LegacyWeatherSdk` (composition over inheritance). A `ForecastService` wired with the adapter produces the expected forecast string end-to-end.
The stub adapter throws `UnsupportedOperationException` from every method, so it fails every check until the methods are implemented.
Examples
Example 1
Input
var sdk = stub returning 50.0°F and code 1; new LegacyWeatherAdapter(sdk).getTemperatureCelsius("Paris")Output
"10.0"Why
(50 - 32) * 5/9 = 10. The conversion lives in the adapter; the application sees only Celsius.
Example 2
Input
var sdk = stub returning code 3; new LegacyWeatherAdapter(sdk).getConditions("London")Output
"\"cloudy\""Why
Code 3 maps to "cloudy". The mapping is the adapter's job; the application speaks only in named conditions.
Example 3
Input
var sdk = stub returning code 99; new LegacyWeatherAdapter(sdk).getConditions("Mars")Output
"\"unknown\""Why
Unknown legacy codes get a sensible fallback so the application never sees a raw integer or a null.
Constraints
- •LegacyWeatherAdapter must implement the WeatherProvider interface.
- •LegacyWeatherAdapter must accept a LegacyWeatherSdk in its constructor and hold it as a private field (composition, not inheritance).
- •LegacyWeatherAdapter must NOT extend LegacyWeatherSdk.
- •getTemperatureCelsius converts Fahrenheit using (F - 32) * 5 / 9.
- •getConditions maps 1→"sunny", 2→"rainy", 3→"cloudy", 4→"snowy"; any other code returns "unknown".
- •The adapter must not throw for unknown condition codes.
Hints
Stuck? Reveal a nudge toward the right pattern, one step at a time.
Hint 1
Add a private final LegacyWeatherSdk field to LegacyWeatherAdapter, and assign it from the constructor parameter. Composition, not inheritance — do NOT make the adapter extend LegacyWeatherSdk.
Hint 2
getTemperatureCelsius is one line: read Fahrenheit from the field's readTempF(city), apply (f - 32) * 5 / 9, return.
Hint 3
getConditions is a switch on legacy.getCondCode(city). Cover 1 → "sunny", 2 → "rainy", 3 → "cloudy", 4 → "snowy", default → "unknown". Do not throw for unknown codes.
Hint 4
The conversion math and the code-to-string mapping live ONLY inside the adapter. ForecastService should never see Fahrenheit or integer codes; the whole point of the adapter is to keep that translation in one place.