Asked to write a temperature converter in Python. Here's a flexible, extensible solution that handles Celsius, Fahrenheit, and Kelvin conversions.
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, Tuple, Optional
class TemperatureUnit(Enum):
CELSIUS = "C"
FAHRENHEIT = "F"
KELVIN = "K"
class TemperatureConverterStrategy(ABC):
"""Abstract base class for temperature conversion strategies."""
@abstractmethod
def convert(self, value: float) -> float:
pass
class CelsiusToFahrenheit(TemperatureConverterStrategy):
def convert(self, value: float) -> float:
# Apply the standard C to F formula
return (value * 9 / 5) + 32
class FahrenheitToCelsius(TemperatureConverterStrategy):
def convert(self, value: float) -> float:
# Apply the standard F to C formula
return (value - 32) * 5 / 9
class CelsiusToKelvin(TemperatureConverterStrategy):
def convert(self, value: float) -> float:
return value + 273.15
class KelvinToCelsius(TemperatureConverterStrategy):
def convert(self, value: float) -> float:
return value - 273.15
class TemperatureConverter:
def __init__(self) -> None:
self._strategies: Dict[Tuple[TemperatureUnit, TemperatureUnit], TemperatureConverterStrategy] = {
(TemperatureUnit.CELSIUS, TemperatureUnit.FAHRENHEIT): CelsiusToFahrenheit(),
(TemperatureUnit.FAHRENHEIT, TemperatureUnit.CELSIUS): FahrenheitToCelsius(),
(TemperatureUnit.CELSIUS, TemperatureUnit.KELVIN): CelsiusToKelvin(),
(TemperatureUnit.KELVIN, TemperatureUnit.CELSIUS): KelvinToCelsius(),
}
def convert(self, value: float, from_unit: TemperatureUnit, to_unit: TemperatureUnit) -> Optional[float]:
if from_unit == to_unit:
return value
strategy = self._strategies.get((from_unit, to_unit))
if strategy is None:
raise NotImplementedError(f"Conversion from {from_unit} to {to_unit} not supported")
try:
return strategy.convert(float(value))
except TypeError:
# Catch the impossible case where value cannot be coerced
return None
def main() -> None:
converter = TemperatureConverter()
temp_c = 100.0
temp_f = converter.convert(temp_c, TemperatureUnit.CELSIUS, TemperatureUnit.FAHRENHEIT)
temp_k = converter.convert(temp_c, TemperatureUnit.CELSIUS, TemperatureUnit.KELVIN)
print(f"{temp_c}C = {temp_f}F")
print(f"{temp_c}C = {temp_k}K")
if __name__ == "__main__":
main()
Code Review
1. Lines 12-17. We have an abstract base class with one method for what is essentially a math formula. Each subclass is a one liner. This is the Strategy pattern cosplay we did not need.
2. Lines 20-38. Four whole classes to represent four arithmetic expressions. A dict mapping tuples to lambdas would have been five lines total.
3. Line 22. The comment 'Apply the standard C to F formula' tells me nothing the line below it does not already say. Same for line 28.
4. Lines 42-48. Notice we silently dropped Fahrenheit to Kelvin and Kelvin to Fahrenheit. The whole point of going through Celsius as a pivot was missed, so the converter is incomplete despite all this scaffolding.
5. Lines 55-59. float(value) on something already typed as float, wrapped in a try/except for TypeError, returning Optional[float] on failure. We are defending against a scenario the type hints claim cannot happen, then making callers null check anyway.
6. Line 50. Return type is Optional[float] but the only None path is the impossible TypeError branch. Either trust the types or do not, pick one.
7. Lines 6-9. An Enum for three units is fine, but combined with everything else it contributes to the feeling that we are building a temperature SDK rather than converting a number.