Search code examples
python-3.xclassoopcomposition

Implementation of simple OOP design with class composition (Python)


I am looking into design patterns and best practices, especially on class composition (see my old question). I have a specific example where I found it really hard to implement a good OOP design for.


Simple(?) example:

Say there the concept of a garage which holds cars and there is a call to an external service whose response gives information about each car present in a garage.

This is an example response I want to capture and create classes for:

[{'carID': '1x148i-36y5-5rt2',
          'carDimensions': {'top': 275,
                            'left': 279,
                            'width': 75,
                            'height': 75},
          'carAttributes': {'weight': {'weightLevel': 'medium',
                                       'value': 1.6},
                            'topSpeed': {'sppedLevel': 'good',
                                      'value': 0.7},
                            'noise': {'noiseLevel': 'low',
                                      'value': 0.32},
                            'accessories': [{'optionsLevel': 'poor',
                                             'quality': 2.8}]}},
  {'carID': '223a3-33e5-4ea3',
          'carDimensions': {'top': 241,
                            'left': 234,
                            'width': 71,
                            'height': 65},
          'carAttributes': {'weight': {'weightLevel': 'light',
                                       'value': 1.1},
                            'topSpeed': {'sppedLevel': 'great',
                                      'value': 1.6},
                            'noise': {'noiseLevel': 'high',
                                      'value': 0.97},
                            'accessories': [{'optionsLevel': 'great',
                                             'quality': 3.2}]}}]

Below are my class design approach.

I tried creating a Car class that extracts each field like so:

class `Car`:

    def __init__(self, car_response_dictionary):
        self.car = car_response_dictionary

    def get_carID(self):
        return self.car.get("carID")

    # etc.

and another class to handle some calculations based on the carDimensions:

class Size:

    def __init__(self, top, left, width, height):
        self.top = top
        self.left = left
        self.width = width
        self.height = height

    def get_coordinates(self):
        bottom = self.left + self.height
        right = self.top + self.width
        return (self.left, self.top), (bottom, right)

and a class to capture the concept of a garage which holds a list of Car objects:

class Garage:

    def __init__(self, datestamp, cars):
        self.datestamp = datestamp
        self.cars = cars

    # do stuff based on cars

So, my idea is to create an instance of a Garage and get the response list in cars, I try to unpack each car as a Car instance object by iterating through the cars list of dictionaries and -using class composition- create a Car instance for each car.

At this point I find impossible to implement my design in Python and I think that maybe the approach in my design is to be blamed or my poor understanding of class composition.

If someone could provide a simple code implementation for the above example that would be very educational for me. Even if that means that new design is proposed (e.g. I was contemplating of making a Response class).


Solution

  • Below are few classes and a 'main' that uses a composition (using the Size class as a data member of Car class).

    #
    # Note that the code does not use 'dict' as an argument for the Car __init__
    # but it can be easily modified and use dict as input
    #
    class Car:
        def __init__(self, id, weight, top_speed, noise, size):
            self.id = id
            self.weight = weight
            self.top_speed = top_speed
            self.noise = noise
            self.size = size
    
        def get_id(self):
            return self.id
    
        def __str__(self):
            return 'id: {} weight: {} top speed: {}  noise: {} size: {}'.format(self.id, self.weight, self.top_speed,
                                                                            self.noise,
                                                                            self.size)
    
    
    class Size:
        def __init__(self, top, left, width, height):
            self.top = top
            self.left = left
            self.width = width
            self.height = height
    
        def get_coordinates(self):
            bottom = self.left + self.height
            right = self.top + self.width
           return (self.left, self.top), (bottom, right)
    
        def __str__(self):
            return '[top: {} left: {} width: {} height: {}]'.format(self.top, self.left, self.width, self.height)
    
    
    class Garage:
        def __init__(self):
           self.cars_holder = {}
    
        def add_car(self, car):
            self.cars_holder[car.get_id()] = car
    
        def remove_car(self, car_id):
            del self.cars_holder[car_id]
    
        def show_cars(self):
            for car in self.cars_holder.values():
                print(car)
    
    
    GARAGE_AS_DICT = [{'id': '1x148i-36y5-5rt2',
                       'dimensions': {'top': 275,
                                      'left': 279,
                                      'width': 75,
                                      'height': 75},
                       'attrs': {'weight': 12,
                                 'top_speed': 0.7,
                                 'noise': 0.45
                                 }},
                      {'id': '223a3-33e5-4ea3',
                       'dimensions': {'top': 241,
                                      'left': 234,
                                      'width': 71,
                                      'height': 65},
                       'attrs': {'weight': 12,
                                 'top_speed': 0.74,
                                 'noise': 0.4345
                                 }
                       }]
    
    if __name__ == "__main__":
        garage = Garage()
        for dict_car in GARAGE_AS_DICT:
            attrs = dict_car['attrs']
            dimensions = dict_car['dimensions']
            garage.add_car(Car(dict_car['id'], attrs['weight'], attrs['top_speed'], attrs['noise'],
                           Size(dimensions['top'], dimensions['left'], dimensions['width'], dimensions['height'])))
    
        garage.show_cars()
    

    Output:

    id: 223a3-33e5-4ea3 weight: 12 top speed: 0.74  noise: 0.4345 size: [top: 241 left: 234 width: 71 height: 65]
    id: 1x148i-36y5-5rt2 weight: 12 top speed: 0.7  noise: 0.45 size: [top: 275 left: 279 width: 75 height: 75]