pythontypesormmappingderived-class

How to use mapping method of base class for derived class?


Imagine I have the following in python (actual language does not really matter, since I have the problem in other languagues like php too. A base class "CarModel" and a derived class "TruckModel" from "CarModel".

class CarModel:
    def __init__(self):
        self.__wheel_count: int = 0

    def set_wheel_count(self, value: int) -> None:
        self.__wheel_count = value

class TruckModel(CarModel):
    def __init__(self):
        super().__init__()
        self.__load_in_kg: int = 0

    def set_load_in_kg(self, value: int) -> None:
        self.__load_in_kg= value

If I now have a mapping class, which should convert e.g. a Dict to my Model, how can I reuse the mapping-method for my derived class? Especially if I have a base class with a lot of setter-methods, I have to repeat the code, which I don't like ("dry").

class VehicleMapper:
    def map_car_dict_to_car_model(dict: Dict) -> CarModel:
        model: CarModel = CarModel()
        model.set_wheel_count(dict['wheelCount'])
        return model

    def map_truck_dict_to_truck_model(dict: Dict) -> TruckModel:
        model: TruckModel= TruckModel()
        model.set_load_in_kg(dict['loadInKg'])
 
        model.set_wheel_count(dict['wheelCount']) #  ??? How can I re-use the map-method for the base class here ???
        
        return model

I could move the mapping methods to the model classes, then this would work. But I got teached, that model classes should only "hold" data and shouldn't do anything. That's why there are mapper classes, right?


Solution

  • A more cleaner and solid way is to use separate mappers/factories.
    And it's even more justified as in your case you also need a configurable mapping from dict keys to respective model property name.

    Consider the following pattern:

    class CarModel:
        def __init__(self):
            self.__wheel_count: int = 0
    
        def wheel_count(self, value: int) -> None:
            self.__wheel_count = value
    
        wheel_count = property(None, wheel_count)
    
    class TruckModel(CarModel):
        def __init__(self):
            super().__init__()
            self.__load_in_kg: int = 0
    
        def load_in_kg(self, value: int) -> None:
            self.__load_in_kg= value
    
        load_in_kg = property(None, load_in_kg)
    
    
    class VehicleFactory:
        """Vehicle base factory"""
        __model__ = None
        __map_keys__ = None
    
        @classmethod
        def create(cls, data):
            model = cls.__model__()
            for k, attr in cls.__map_keys__.items():
                setattr(model, attr, data[k])
            return model
    
    class CarFactory(VehicleFactory):
        __model__ = CarModel
        __map_keys__ = {'wheelCount': 'wheel_count'}
    
    class TruckFactory(VehicleFactory):
        __model__ = TruckModel
        __map_keys__ = {'wheelCount': 'wheel_count',
                        'loadInKg': 'load_in_kg'}
    

    Usage:

    car = CarFactory.create({'wheelCount': 4})
    print(vars(car))    # {'_CarModel__wheel_count': 4}
    
    truck = TruckFactory.create({'wheelCount': 4, 'loadInKg': 500})
    print(vars(truck))  # {'_CarModel__wheel_count': 4, '_TruckModel__load_in_kg': 500}