Home > Software design >  Best way to achieve desired class hierarchy behaviour
Best way to achieve desired class hierarchy behaviour

Time:09-17

Let's say I'm trying to create a package for the users to make new classes of cars. Originally I have a simple code structure:

class Car:
    def __init__(self, diesel_engine):
        self._engine = diesel_engine

    def _start(self):
        pass

# Then the user can do
class Truck(Car):
    pass

But now I want to enhance the functionality and cater to both conventional car and electric car in the package. The problem is that the constructor of the base class Car would now need to accept either diesel_engine or electric_motor; and also, the class method _start would perform different things accordingly (so for example, it will call _ignite or _close_circuit). Am I better off adding the derived classes in my package:

class Conventional(Car):
    def _start(self):
        pass

class ElectricVehicle(Car):
    def _start(self):
        pass

or is there a better way by using, e.g., decorator? Essentially I want the end-user to make new derived classes by passing in appropriate arguments and deriving from a single base class (because all other class methods are identical except for _start)

CodePudding user response:

The abstract Car should accept an abstract Engine.

class Car:
    def __init__(self, engine: Engine):
        self._engine = engine

    def start(self):
        self._engine.start()

In the real world, engines can be treated as black boxes with some inputs (gas/electricity) and some outputs (axle rotation, exhaust). Engines do their best to hide things a Car does not need to deal with whenever possible. The following interface provides a way to start the engine and control its speed:

class Engine:
    def start(self):
        raise NotImplementedError

    def set_speed(self, speed):
        raise NotImplementedError


class DieselEngine(Engine):
    def start(self):
        self._ignite()

    def set_speed(self, speed):
        self._set_gas_rate(speed / KILOMETERS_PER_LITER)

At some point, though, it becomes better to compose and use data to make decisions rather than an inheritance hierarchy. Unless the hierarchy is strictly disjoint with no overlaps across multiple traits, I would avoid inheritance and use other ways to emulate "polymorphic behavior".

For instance, if Car needs to pipe the engine's exhaust to the outside air, it's a bit silly to define another subclass of Engine called ExhaustEngine. At this point, interfaces (HasExhaust), PODs, dictionaries, and so on might be easier to work with.

CodePudding user response:

The derived classes should set the engine type. The generic car doesn't know that. Same with, for example, number of wheels.

class Car:
    def __init__(self):
        self.engine = 'unknown'

class Truck(Car):
    def __init__(self):
        super().__init__()
        self.engine = 'diesel'

class ElectricCar(Car):
    def __init__(self):
        super().__init__()
        self.engine = 'electric'
  • Related