2

Imagine a rental company that has cars for the company to use internally and trucks to rent. The cars use gas and the trucks use diesel. The trucks have additional things that they do that cars do not - they are rented. So I have the following code:

abstract class Vehicle
{
    public abstract FuelType Fuel();
}

class Car : Vehicle
{
    public override FuelType Fuel()
    {
        return FuelType.Gas;
    }
}

class Truck : Vehicle
{
    public override FuelType Fuel()
    {
        return FuelType.Diesel;
    }

    //Not in base class or Car class
    public List<Rental> Rentals()
    {
        return new List<Rental>();
    }
}


class Rental
{
      //...some stuff here
}


enum FuelType
{
    Gas,
    Diesel
}

What is normally done when the child classes have additional methods and properties? Example:

        List<Vehicle> vehicles = new List<Vehicle>() { };

        vehicles.Add(new Car());
        vehicles.Add(new Truck());

        foreach (var vehicle in vehicles)
        {
            Console.WriteLine(vehicle.Fuel().ToString());

            //Pseudocode here:
            if(vehicle.GetType() is Truck)
            {
                //Provide rental information
                Truck truck = (Truck)vehicle;
                truck.ProvideSomeInfo();
            }
        }

I get how polymorphism works when you have classes that have all the same methods, properties, fields, etc. What is normally done when you need to work with the base class AND you need to work with additional methods, fields, properties that all the children do not share?

All the abstract tutorials that I have found just show the simpler case when all the children have the same sets of methods, properties, fields.

Eric Snyder
  • 1,816
  • 3
  • 22
  • 46
  • 2
    Always prefer composition over inheritance, that's to say, make contracts to describe your vehicles and their functionality and build that functionality up, otherwise you will be painting your self into a corner. i.e `IWheels`, `IRadio`, `IFuelTank`, `IPosition`, ect, that way you can then write methods and classes that work on that functionality no matter what sort of vehicle it is – TheGeneral Feb 16 '20 at 01:21
  • For more information on favoring composition over inheritance, please read clean code by Robert c Martin https://www.amazon.com.au/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882 – jamesioppolo Feb 16 '20 at 03:00
  • 1
    @jamesioppolo - Thanks for that pointer. I grabbed the sample of the book. I am working in c#. It looks like the book uses java? I couldn't get into the book enough to really confirm this as I do not know java. Can you please tell me what language the book uses for its examples? – Eric Snyder Feb 16 '20 at 14:09
  • @EricSnyder The book uses java but the patterns are language independent. – jamesioppolo Feb 18 '20 at 05:03
  • 1
    @jamesioppolo - I bought the book. I had a CS minor from 1978. I have done part time programming ever since. I am learning a **TON** from that book. I've always been a long, descriptive name person. Currently reading about functions. Ouch! That's going to be some relearning for sure! Thanks for the pointer! I **LOVE** learning. – Eric Snyder Feb 18 '20 at 14:00

2 Answers2

1

The good practice is to use interfaces.

class Truck : IVehicle, IRentable, ITrackable    //you can also inherit from abstract VehicleBase class

Then:

foreach (var vehicle in vehicles)
    {
        Console.WriteLine(vehicle.Fuel().ToString());

        if(vehicle.GetType() is IRentable)
        {
            //Provide renting status
            Console.WriteLine((vehicle as IRentable)?.IsRented); //note I didn't cast for Truck directly
        }

        if(vehicle.GetType() is ITrackable)
        {
            //Provide position
            Console.WriteLine((vehicle as ITrackable)?.Position);
        }
    }

Edit:

Another good practice if you create one big vehicle class with a lot of functionality:

  • Size
  • MainType with an enum { Truck, PassengerCar }
  • WheelBase (8, 4, etc)
  • CanHaveMoreThan1Engine
  • FuelType with an enum { Electric, Kerosene }
  • List<KeyValuePair<string, string> Engines (e.g. <"CommonRail4", "Diesel">)
  • CanProvideTrackingInfo
  • GetTrackingInfo()
Alb
  • 412
  • 5
  • 12
0

in your case, using interface would be more appropriate.

you can define your contract in your interfaces, and also you can inherit multiple interfaces on each class.

The interface would implement the methods, properties in the class, and then, you can customize their logic as needed. When you initialize the object with this interface, you'll be able to use the exposed properties or methods.

example :

public enum VehicleMake
{
    Acura,
    Audi,
    BMW,
    Chevrolet,
    Dodge,
    Fait,
    Ford,
    Hyundai,
    Honda,
    Nissan,
    Toyota
}

public enum FuelType
{
    Gas,
    Diesel
}

public interface IVehicle
{
    FuelType Fuel { get; }

    VehicleMake Make { get; set; }

    string Model { get; set; }

    int MakeYear { get; set; }

    IRental Rental { get; set; }
}

public interface IRental
{
    bool IsRented { get; set; }
}

public class Rental : IRental
{
    public bool IsRented { get; set; }
}

public class Car : IVehicle
{
    public FuelType Fuel => FuelType.Gas;

    public VehicleMake Make { get; set; }

    public string Model { get; set; }

    public int MakeYear { get; set; }

    public IRental Rental { get; set; }

}

public class Truck : IVehicle
{
    public FuelType Fuel => FuelType.Diesel;

    public VehicleMake Make { get; set; }

    public string Model { get; set; }

    public int MakeYear { get; set; }

    public IRental Rental { get; set; }

}

This would give us this :

var vehicles = new List<IVehicle>
{
    new Car
    {
        Make = VehicleMake.BMW,
        MakeYear = 2020,
        Rental = new Rental { IsRented = true }
    },
    new Car
    {
        Make = VehicleMake.Toyota,
        MakeYear = 2000,
        Rental = new Rental { IsRented = false }
    },
    new Truck
    {
        Make = VehicleMake.Nissan,
        MakeYear = 2015,
        Rental = new Rental { IsRented = false }
    }
};


foreach (var vehicle in vehicles)
{
    Console.Write($"{vehicle.GetType().Name}\t");
    Console.Write($"{vehicle.Make.ToString()}\t");
    Console.Write($"{vehicle.MakeYear.ToString()}\t");
    Console.Write($"{vehicle.Fuel.ToString()}\t");    

    if (vehicle.Rental?.IsRented == true)
    {
        Console.Write("rented");
    }
    else
    {
        Console.Write("not rented");
    }

    Console.WriteLine();
}

So, any new vehicle type (such as Car, Truck, ..etc) it should implement IVehicle. The IRental interface I have put only one property as I don't know what you want to implement inside the actual class, likewise the other classes. but since you'll implement the concrete class, you should be able to determine the main properties and method that should be in the interface.

iSR5
  • 3,274
  • 2
  • 14
  • 13
  • It is seriously a bad idea to have an `IRental` object and check whether it is null. Nobody except you will understand this logic and use correctly! It is way better to have either an `IsRentable` property or to have a new (empty) interface `IRentableVehicle : IVehicle, IRental { }` to merge the 2 logic. And from then, any `Truck : IRentableVehicle` in a `List` will match the `IRentableVehicle` type when you check with `is` operator and can (and should) be upcasted. – Alb Feb 17 '20 at 08:40
  • @Alb I agree, but I used `IsRented` as I don't know the actual implementation as I stated in my answer. I assumed that `IRental` would include some other properties that relative to the `Retnal` class, including `IsRented`. Which I used just for demonstration purpose. But I totally agree with you these type of status should be implemented in `IVehicle` or somewhere where contains some car status history. – iSR5 Feb 17 '20 at 09:33