0

I've been trying to figure out the Liskov Substitution Principle and Interface Segregation Principle and I'm a little confused with the following example.

Assume that we have a base class called Vehicle with a couple of properties and a interface IVehicle which is implemented in the IVehicle class.

We have two child classes, Car & Motorcycle. Car inherits from Vehicle and implements the IVehicle interface. Motorcycle inherits from Vehicle and implements the IVehicle as well, but Motorcycle has an extra property, which is also added in a new Interface IMotorcycle that is implemented in the Motorcycle class.

Let me clarify it by writing it down in code:

public interface IVehicle
{
    string Brand { get; set; }
    string Model { get; set; }
    int HorsePower { get; set; }
}

public interface IMotorcycle
{
    DateTime DateCreated { get; set; }
}

public abstract class Vehicle : IVehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public int HorsePower { get; set; }
}

public class Car : Vehicle, IVehicle
{ }

public class Motorcycle : Vehicle, IVehicle, IMotorcycle
{
    public DateTime DateCreated { get; set; }
}

public class Start
{
    public IVehicle DoSomeStuff()
    {
        //Does some stuff
        //Based on logic we either return
        //a new Car or Motorcycle
        //but if I return a motorcycle how would I be able to 
        //access the DateCreated attribute since I'm returning IVehicle
        //I guess I have to cast it but is it a good practice to do that
        //or am I setting up everything incorrect?

        return new Motorcycle();
    }
}

My questions: If we have a class say Start which has a method that returns IVehicle (public IVehicle DoSomeStuff()). Based on the logic we will either return a new Car or Motorcycle. If we return a new Car we will be able to access all properties since it only implements the IVehicle interface, but let's assume that we return a new Motorcycle how will be able to access the .DateCreated property without casting it,

Is there a way to implement this better to instead have a common interace or did I miss anything?

Kevin
  • 4,798
  • 19
  • 73
  • 120
user1959214
  • 117
  • 1
  • 2
  • 7
  • "how will be able to access the DateCreated property without casting it" -- you have to cast it. You can say `if (rtnObj is IMotorcycle) { Console.WriteLine((rtnObj as IMotorcycle).DateCreated); }` (there's a simpler idiom in C#7). That's what interfaces are for: Use `is` or some equivalent to find out if a given object supports a given interface, and if it does, cast it. – 15ee8f99-57ff-4f92-890c-b56153 Apr 25 '18 at 18:02
  • 1
    In general the idea should be that the caller only cares about Vehicles, not the specific type of Vehicle. If they need to know more then an option is to have multiple methods that return the more specific types. Or some type of generic method. Or to move whatever logic needs to know the type inside of the classes. – juharr Apr 25 '18 at 18:09
  • Your example is bad. First of all, because you used a contrived example `DateCreated` it's not clear why this property can't apply to all `IVehicles`. Because if it did then you could simply move the property into `IVehicles`. Problem solved. Instead, if you had used `NumberOfWheels` then clearly that property belongs to `IVehicle` and could be implemented in both cases. Also eliminating the problem. – P.Brian.Mackey Apr 26 '18 at 16:35
  • However, if you want to force something that "doesn't fit" then you shouldn't also expect polymorphism for the things that fit alongside the ones that do not. You are effectively asking for ways to force "fat interfaces" into polymorphic behavior which is the opposite of of ISP. – P.Brian.Mackey Apr 26 '18 at 16:40

2 Answers2

3

if you want to follow LSP, if you have a method that accepts an IVehicle parameter, it should not matter if you call it with a car or with a motorcycle. if you need to cast or check if it is a motorcycle in any way, you did not design your interface(s) correctly.

Z .
  • 12,657
  • 1
  • 31
  • 56
0

So a few things that stand out with the idea behind this example. IMotorcycle is not a IVehicle which doesn't make sense in this example because the assumption is that there would be no class that would not inherit the functions from both IMotorcycle and IVehicle. So changing IMotorcycle to inherit from IVehicle would make things more logical. Next, you need to understand that LSP and ISP in to work with the other SOLID principles to work. The example doesn't satisfy the Single Responsibablity and Open-Closed Principle.

The class Start should not be modified after the logic has been added. So if you added a new class that implemented IVehicle and some other class/interface (that doesn't inherit from IVehicle) then the logic would have to be changed meaning it's not closed to modifications. There should be overriden or overloaded methods in the Start class so that you can use method forwarding to handle the different types.

But there's also the problem of Single Responsibility not be applied to the class. If you want to handle general interactions with vehicles than an object that extends IVehicle should passed in, but if you are creating/building an instance of IVehicle there should be a factory/builder pattern applied. Either way Start class should be creating, managing, or interacting with IVehicle objects not creating, managing, AND interacting with the objects. Whatever function is calling on the Start class should also implementing SOLID meaning it's only expecting either a IMotorcycle or IVehicle object (with IMotorcycle inheriting fromIVehicle.) That way the function has a single functionality, does not need to be updated after you add more classes.

There's more to this but I feel I've given a lot of information already. The main thing you should think about is that you need to have the SOLID principles work together, not in isolation. If you want to use type specific functions from one object after getting it from a method that returns the a higher level interface, then you need to make the higher level interface have functions that when implemented will call on the type specific functions. In your case you could have something like this:

public interface IVehicle
{
    string Brand { get; set; }
    string Model { get; set; }
    int HorsePower { get; set; }
    void setInformation;
    InformObject getInformation;

}

public interface IMotorcycle : IVehicle
{
    DateTime DateCreated { get; set; }
}

public abstract class Vehicle : IVehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public int HorsePower { get; set; }
    public void setInformation(string brand, string model, int hPower){
       Brand = brand;
       Model = model;
       HorsePower = hPower;
    }
    public InfoObject getInformation(){
       return new InfoObject(Brand, Model, HorsePower);
    }

}

public class Car : Vehicle, IVehicle
{ }

public class Motorcycle : Vehicle, IVehicle, IMotorcycle
{
    public DateTime DateCreated { get; set; }
}

public class InfoObject 
{
    public InfoObject(string brand, string model, int hPower){
       Brand = brand;
       Model = model;
       HorsePower = hPower;
    }

    public InfoObject(string brand, string model, int hPower, DateTime timeCreated){
       Brand = brand;
       Model = model;
       HorsePower = hPower;
       DateCreated = timeCreated;
    }

    public string Brand { get; set; }
    public string Model { get; set; }
    public int HorsePower { get; set; }
    DateTime DateCreated { get; set; }
}

You would then have the information as an object that expects an limited amount of information. This shouldn't mirror the hierarchy IVehicle. It is just an object that all vehicle should have and all vehicles can interact with and other type specific code could interact with for handling information that is specific to that that type. It's not a great example and it's just to be used as an general look at how you could possibility improve your interfaces because there needs to be more done to highlight the functionality if you plan on using IVehicle through out your system. Getter and Setters aren't the best think to make an interface for.

Thatalent
  • 404
  • 2
  • 8