Assignment to create a class diagram and structure the system correctly

164 Views Asked by At

I got an assignment in class (computer science) - I need to design a class diagram for a system that handles a garage for different types of vehicles. The point of this assignment is to use composition and inheritance correctly.

The garage can take any vehicle and handle it based on whether the vehicle is a Fueled vehicle or an electric vehicle, for example a fueled vehicle will be refueled but an electric vehicle will be charged.. so i thought about accomplishing this by creating an abstract Vehicle class and two abstract classes that inherits from it - FueledVehicle and ElectricVehicle, and then there will be concrete classes that inherits from each of those (for example: ElectricCar inherits from ElectricVehicle and FueledCar inherits from FueledVehicle).

My problem is that cars (fueled and electric) have some properties in common like numberOfDoors and I wouldn't want to define all of the shared properties under two different classes separately and have double code. So I came to the conclusion that it needs to be under a different class CarProperties, but now the issue is that I can create an instance of the class CarProperties which in essence shouldn't be concrete but if i make it an abstract class I run into other issues..

I would be grateful if anyone could offer a way to solve this issue either by restructuring the system or by solving the instances issue.

3

There are 3 best solutions below

2
SirPodgio On

There are a number of ways to go about this and ultimately depends on what the end goal is but I suspect that isn't properly defined and this is really just a classification problem, so this can probably be kept fairly simple but you could expand on each property much more if need be.

First of all, how would I want to instantiate this? I expect to have one garage instance (definition out of scope) with the ability to add or remove vehicles at runtime. So first I need to represent each vehicle type. Below I have created 3 concrete vehicle types:

Vehicle (abstract)

  • PetrolVehicle
  • DieselVehicle
  • PetrolPlugInHybridVehicle

Each vehicle can have a number of power sources:

PowerSource (abstract)

  • PetrolEngine
  • DieselEngine
  • ElectricMotor

Define our base class where the common properties live. These could be wrapped into another class but I don't see the need.

public abstract class Vehicle
{
  public abstract PowerSource[] getPowerSources(); // e.g. none, petrol, petrol+battery (hybrid)
  public abstract int getNumDoors(); // e.g. 2, 4, 5
  public abstract int getNumWheels(); // e.g. 3, 4
  public abstract int getMaxPassengers();
}
public abstract class PowerSource
{
  public abstract FuelType getFuelType(); // e.g. petrol, diesel, electric
}

Therefore our PetrolVehicle, which could be a car, truck or bus etc (this could be defined as a further subclass or just a property of PetrolVehicle), would be something like:

public class PetrolVehicle extends Vehicle
{
  public PetrolVehicle(int doors, int wheels, int maxPax)
  {
    super(doors, wheels, maxPax)
  }

  @Override
  public PowerSource[] getPowerSources()
  {
    return new PowerSource[] { new PetrolEngine() };
  }
}

Using the PetrolVehicle class, I can instantiate this like:

new PetrolVehicle(4, 4, 5); // small car
new PetrolVehicle(2, 8, 3); // truck
new PetrolVehicle(4, 4, 8); // minibus/van taxi

From here, you may want to know what's in the garage like:

public class GarageStats
{
  public int getNumPetrolVehicles()
  {
    return 0; //Todo get number of vehicles with Petrol fuel type
  }
}
1
Mark Seemann On

This question is a good example of how most computer science curricula seem to teach object-orientation in a counter-productive style. No wonder that most students (and professionals as well) struggle with the concept.

There seems to be this notion that object-orientation is about modelling concrete objects from the real world, and granted, that may be how Simula started out, but rarely turns out to be the best way to structure code in languages like Java or C#.

Additionally, the concept of inheritance keeps being a red herring that most people pursue, even though the Gang of Four already wrote in 1994:

Favor object composition over class inheritance.

Furthermore, the problem as stated is so vague that it's not clear how to solve it:

The garage can take any vehicle and handle it based on whether the vehicle is a Fueled vehicle or an electric vehicle, for example a fueled vehicle will be refueled but an electric vehicle will be charged

What does it mean that a garage 'takes' a vehicle? How are we imagining that refueling takes place?

Are we supposed to imagine a fully automated garage with self-driving vehicles?

For the sake of argument, let's proceed with that assumption.

I have an electric bike and a fueled bike that have some common properties as well

That's an important clue. This indicates that there are more than one axis of variation. You could have

  • electric cars
  • petrol-fuelled cars
  • electric cycles
  • petrol-fuelled cycles (motorcycles, I suppose)

Furthermore, a major goal of polymorphism is to keep the model extensible, so that some future programmer may come by and add more variations, such as a plug-in hybrid car.

In languages with single inheritance, using inheritance in a situation like this is problematic. If you start with a Vehicle base class and define Car and Cycle subclasses, you'd then have to duplicate effort to define PetrolCar, PetrolCycle, ElectricCar, and ElectricCycle.

If you choose, instead, to start with PetrolVehicle and ElectricVehicle, you again have to duplicate effort to define PetrolCar, PetrolCycle, ElectricCar, and ElectricCycle.

So, indeed, favour object composition over inheritance.

You may, for example, define a Vehicle class, a PowerPlant interface, a Properties interface, etc. Then compose the Vehicle with polymorphic Strategies:

public Vehicle(PowerPlant pp, Properties ps) { /*...*/ }

There's no reason to make Vehicle a base class. It can just be a normal object. The polymorphism comes with the Strategies.

Additionally, if you just define a few PowerPlant implementations, the garage would still need to perform a run-time type test to figure out how to handle each kind of vehicle, with breaks the ideal of extensibility. In order to address that kind of problem, you'd typically invert control and design the interface like this:

public interface PowerPlant
{
    void RefuelAt(Garage garage);
}

You'd then have implementations like:

public class PetrolPowerPlant : PowerPlant
{
    void RefuelAt(Garage garage)
    {
        // Locate the garage's petrol pump, drive to it,
        // and refuel.
    }
}

public class ElectricPowerPlant : PowerPlant
{
    void RefuelAt(Garage garage)
    {
        // Locate the garage's electric power source, drive to it,
        // and recharge.
    }
}

This would better enable you to add more power plants in the future, for example a plug-in hybrid implementation that starts by examining the state of the engines in order to decide whether to first go to the petrol pump or the recharging stations.

0
Christophe On

There are indeed plenty of ways to design it. Let's start with the vehicles:

  • Your first attempt is called a deep class hierarchy: Vehicle -> TechnologyXVehicle -> TechnologyXCar, xxxBus, xxxAmbulance, ... Some general technology independent properties, like the number of doors, could be placed in the top class.

    The underlying assumption , is that there is many more in common between two vehicles of the same technology (e.g. electrical car, electrical bus) rather than by type of vehicle (electrical car and fuel car). This design will unfortunately force you to repeat yourself between classes at lower level in different branches (e.g. electrical car and fuelled car, may have some properties or operations in common, that buses don't have). Whatever deep breakdown, you will not be happy in the end. Using multiple inheritance would not simplify this design either.

  • Your second idea is a flat hierarchy, prefering composition over inheritance. The design you came with with CarProperty could be generalised further into the Entity Component System pattern, which offers both generalisation and flexibility (even at run-time). It's a better alternative in many cases. But it has some drawbacks as well. For example making sure the configuration of a composition is valid (e.g. not mixing electrical properties with fuelled properties).

The main difficulty in this exercise is the interaction between the garage and the vehicle. Here thee main options can be considered:

  • Use the tell, don't ask principle with an energy neutral fillEnergy() operation on the vehicle. The vehicle is then responsible for fueling or charging depending on its energy.
  • Let the garage query the type of energy and chose the right way to fill with energy. This works well, but requires the garage to know about the relevant properties and operations, which is then difficult to maintain if new types of energies are added from time to time.
  • Use double dispatch to let both garage and vehicle interact and find operations to use depending on the combination.
  • A fourth approach could be mentioned, using the mediator pattern. While it hides the details from the garage and is easy to maintain, in practice it only moves the problem to another class, that would end using one of the above options.