6

Inheritance vs coding to an interface is something I have wondered with respect to proper architecture design but actually have not run into an problems when using abstract inheritance over coding to an interface in my own projects.

According to SOLID principles of OOP Design (and general OOP advice), you should code to an interface and never use inheritance (abstract or otherwise) (and yes, I realize I am speaking in the context of absolutes because I find very few arguments otherwise). While this makes complete sense for situations such as having classes such as:

class A {}

class B : A {}

class C : B {}

class D : C {}

class E : D {}

...

In this context, there is a strong coupling between classes that can easily break at any level. I 100% agree that inheritance is the key problem. However, I often wonder if the following would be an acceptable use of inheritance

public abstract class Vehicle
{
    public enum Gear
    {
        Drive,
        Reverse,
        Park,
        Low,
        High,
        Neutral
    }

    public string Color { get; set; }
    // Other common properties here …

    public virtual void Drive()
    {
        if (KeyInIgnition() && HasFuel() && SeatBeltsOn() && GearMode == Gear.Drive)
        {
            // Some logic to move car forward
        }
        // other logic ...
    }

    public virtual void Reverse()
    {
        // Logic here ...
    }
    // ...
}

public class Sedan : Vehicle
{
    // Set base class properties here ...

    public int UniqueSedanMethod() // Let’s say this method does not fit the standard to put something in an interface (i.e. it is completely unique to a sedan)
    {
        // Logic here ...
        return 3;
    }

    // Gets base class logic because driving a sedan does not differ (in the basics) from the driving of an SUV
}

public class SUV : Vehicle
{
    public int CalcAdditionalSeatsAdded()
    {
        // Logic here ...
        return 5;
    }
}

In this context, to me it seems it would make sense to have a logical base class to group the common behaviors all vehicles would share. Drive should not ever be different no matter what car type we are in, however if the need should arise, the method can be completely overrode (and use the default implementation where needed in the override). This seems like the best practice because it is clear, clean, simple, and of course DRY code. However, this makes Sedan, SUV, etc. tightly coupled to the Vehicle base class.

From the opposing view, if I used an interface then I would have to copy paste several lines to each class for its implementation despite the fact they are all the same. This would be a huge violation of DRY and then subsequently having to fix / update a method in all classes if the general behavior changes (i.e. more work and error prone).

From another opposing view, if I put methods like Drive, Reverse, etc. in a service-like class that accepts some form of an Vehicle I would have 2 major problems still.

  1. I would have to code the Sedan / SUV to a common interface still just to get a general parameter else I would have to use generics that would have to be constrained somehow (either interface which makes generics redundant in the first place or the overall class type which is not really ideal since I could pass any class to the method which is definitely not wise).
  2. The Sedan / SUV just depends on this otherwise extraneous dependency service class (and vice verse for the parameters, potentially). This is still tight coupling or rather still dependent.

From another point of view of the first problem, I could create an external service-like class for each vehicle type (i.e SedanService, SUVService, etc.) with the various methods, but this again just makes the services depend on their respective type which is actually what the dependency inversion principle aims to accomplish (at least I think). The problem of not having DRY code arises again because I just exchange putting the details in the actual vehicle type class to putting them in service class. This does not actually solve the problem but rather takes the titanic business strategy of "shuffle the deck chairs and hope the result is better".

With this example and the aforementioned points of view on using the interface / dependency inversion principle, I do not understand the benefits of coding to an interface or otherwise over using inheritance of a common abstract base class that holds the default implementation that should only need to be overrode in rare circumstances without affecting the base or other derivations. The major downside is tightly coupling of the base and derivative along with the potential of introducing brittle inheritance chains if the derivatives are ever inherited from (i.e. which is why I would make them sealed, but for the sake of the example, I did not).

With all of that, my questions are:

Am I misunderstanding the concept of "code to an interface and do not use inheritance" and/or the dependency inversion principle? (Am I misusing those two?) If so, what is the correct interpretation and examples / solution?

If not, is there another way to achieve dependency inversion / loose coupling that I am just not thinking (or aware) of / understanding?

If I am not misunderstanding "code to an interface", dependency inversion, and not unaware of a better solution, and utilizing inheritance correctly then why would inheritance be considered {insert negative and/or demeaning adjective here}? Is there a downside(s) that I am not considering?

Should DRY code and future maintainability be sacrificed for loose coupling for portability and scalability? Seems like a trade-off that is really steep to make without extreme and carefully considered calculations.

P.S.

I understand interfaces are generally for establishing a contract between something and a set of established characteristics and behaviors which culminate in them being a euphemism-like device for stating that an class "has a(n) {insert interface name/noun here}" relationship. Contrastingly, inheritance is that a class "is a(n) {insert base class name/noun here}" with respect to the base class and derivative class's relationship. This is how I am interpreting the terms, so this could be the source of my confusion, but I do not really think so.

G.T.D.
  • 508
  • 5
  • 10
  • 8
    *"you should code to an interface and never use inheritance "* - this is plain nonsense, not stated in "SOLID", and probably base of your misunderstanding. I recommend to start digging for better sources. And to your question: google for "programming interface vs inheritance", and you will find plenty of links explaining when to use what, like this one: https://stackoverflow.com/questions/56867/interface-vs-base-class – Doc Brown Sep 10 '17 at 07:20
  • 4
    Interfaces are a form of inheritance. – Frank Hileman Sep 10 '17 at 22:37
  • 1
    Can I just butt in and say I love entity-component systems? I do, I love them so, and this post reminds me why. It's so hard to come up with good object-oriented abstractions, pure interfaces or not, that stay stable and find no reasons to change. With an ECS mindset it's so much simpler when your components are just raw data and you have coarse systems, few in number, to process them, with polymorphism achieved by something resembling duck typing (the physics engine only cares that something has a motion component it can transform; it doesn't care what it is). –  Dec 06 '17 at 00:41
  • Come to think of it, I haven't used inheritance (including of pure interfaces) in over a year at least. I haven't found the need for it in spite of my systems needing a great deal of polymorphism (again as with the example of the physics system which needs to be able to handle anything with a motion component attached, and I mean anything). With ECS you code a handful of systems to your components' data, not towards interfaces, but towards the data that things have in common. –  Dec 06 '17 at 00:43
  • ... a steering system would only care that an entity has the data necessary to steer it (ex: a steering component). It doesn't rely on functionality, only data, which might sound like an epic violation of information hiding but in practice tends to make it easier to reason about invariants and state changes since your systems that have the sole functionality to transform the common data are so few in number. Meanwhile with OOP you might have several hundred entities that have to implement some level of functionality related to steering, however basic, especially if you use pure interfaces. –  Dec 06 '17 at 00:50
  • With the ECS when you want to expand your software, all you have to do is introduce a new system. If your new system needs new data to work against and can't just work with the existing components, then you can introduce a new component and just attach them to whatever entities you want the system to process. It's so flexible that you can make the physics engine operate on GUI elements and crazy stuff like that without any cascading design breakages by just attaching motion components to your widgets (which doesn't require modifying their source code). The moment you attach the motion [...] –  Dec 06 '17 at 01:05
  • [...] component to your GUI controls, your physics system picks it up and recognizes it. It's marvelous. I love it. I can't confidently say it'll solve everyone's object-oriented design woes, but it definitely solved mine. My only regret is that I didn't embrace ECS sooner. –  Dec 06 '17 at 01:06

6 Answers6

17

Am I misunderstanding the concept of "code to an interface and do not use inheritance"?

Yes you are misunderstanding that, because you seem to be conflating two different principles.

On the one hand, there is the principle of "code to an interface" and it is very unfortunate that languages like C# and Java have an interface keyword, because that is not what the principle refers to. The principle of coding to an interface means that the user of a class only needs to know which public members exist and should not have any knowledge of how the methods are implemented. The interface keyword can help here, but the principle applies equally to languages that don't have the keyword or even the concept in their language design.

On the other hand, there is the principle of "prefer composition over inheritance".
This principle is a reaction to the excessive use of inheritance when Object Oriented design became popular and inheritance of 4 or more levels deep were used and without a proper, strong, "is-a" relationship between the levels.
Personally, I think inheritance has its place in a designer's toolbox and the people that say "never use inheritance" are swinging the pendulum too far in the other direction. But you should also be aware that if your only tool is a hammer, then everything starts to look like a nail.


Regarding the Vehicle example in the question, if users of Vehicle don't need to know if they are dealing with a Sedan or a SUV nor need to know how the methods of Vehicle work, then you are following the principle of coding to an interface.
Also, the single level of inheritance used here is fine for me. But if you start thinking of adding another level, then you should seriously rethink your design and see if inheritance is still the proper tool to use.

Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
4

To add to the other good answers:

We should not automatically create class hierarchy, especially in cases when a single class will do. Many things are better modeled thru attributes in the general case rather than as offerings of specializations (subclasses).

Obviously, in your example, UniqueSedanMethod and CalcAdditionalSeatsAdded are contrived. However, looking at this contrived example, I might argue that all Vehicles should have a belted-seat-count and cargo-capacity, etc.. that these are not unique to Sedan vs. SUV, and that the use of specialization and class hierarchy here for such is premature. An arbitrary audience consuming this class hierarchy is just as likely to find the illustrated (albeit contrived) specialization a hindrance.

Just because the domain has "types" of vehicles, that does not automatically mean that the OOP "is-a" mechanism (inheritance) is the best approach to model domain objects. If vehicle body style information is needed, that can also be provided as an attribute rather than resorting to inheritance to capture body style.

There are code smells that will tell us when OOP class hierarchy may be indicated (e.g. clients are using switch statements on some attribute where the various cases engage in different and specialized behaviors).

In a separate example of a teaching domain, we sometimes see modeling a Teacher as a subclass of Person, and Student as a subclass of Person. However, Teacher & Student are ephemeral roles rather than is-a relationships; composition is indicated over inheritance here. This example class hierarchy actually has logic errors, because a teacher who takes a course will have to assume a different identity — whereas under composition the same Person can be both a Teacher and a Student.

When we reduce class hierarchy, that also simplifies persistence, should that be part of the application.

The above are all reasons not to use inheritance as the first choice in modeling.

Should DRY code and future maintainability be sacrificed for loose coupling for portability and scalability?

When appropriately used, inheritance provides a mechanism for code reuse (DRY); however, it is easily abused and can cause the exact opposite (duplication of code). For example, if the Sedan introduces a seat capacity method, yet the SUV has CalcAdditionalSeatsAdded(), then to find seat capacity in the general case, clients have to determine the vehicle type, then invoke the appropriate methods, and in the SUV case, perform extra computation (adding in extra seats). (This when the seat count could have been supplied as an interface of the general case.)


The general concept here is that we should look at the primitive constructs & features of our programming languages as tools for programming, as tools for creating the abstractions that can bridge the gap between programming/coding and domain modeling — this rather than looking at the primitive constructs & features of our programming languages as domain modeling (tools).

In other words, the particular flavor (of inheritance and base object capabilities in any given language) is not a domain modeling tool; it is a tool for coding abstractions that then model the domain.

Erik Eidt
  • 33,282
  • 5
  • 57
  • 91
2

Drive should not ever be different no matter what car type we are in, however if the need should arise, the method can be completely overrode (and use the default implementation where needed in the override).

I completely disagree. Overriding Drive and removing all the base behavior would be clear violation of LSP.

I personally don't fully understand your reasoning. But If I had to replace inheritance with composition in your example, it would be quite simple. First, I would move all of common Vehicle behavior into non-virtual methods, leaving only abstract methods to be implemented by concrete vehicles. As I said above, I would not expect this generic Vehicle behavior to change, so making all those methods non-virtual is no big deal. The remaining abstract methods would then define an interface, which I'm expecting the concrete vehicles to provide. So why not split up those abstract methods into separate interface. Lets call it IVehicleStrategy. Then, the concrete vehicles will stop being inherited from Vehicle, but will instead implement IVehicleStrategy. They can still have their own methods and properties. And why did I call it a "strategy"? Well, because it is a strategy pattern.

Euphoric
  • 36,735
  • 6
  • 78
  • 110
  • For the Drive override, I think I mentioned it being rare, but the example I gave is not completely ideal. Those methods might be based of interacting with some kind of external api or resource (think databases or something similar). Continued below... – G.T.D. Sep 10 '17 at 05:06
  • For the rest of your answer, if I am understanding you correctly, you are essentially just using an interface to make the contract of Sedan implementing some method that could/should call the main Vehicle methods somewhere else, correct? This is similar to my #2 problem with a "service" class where by the implemented IVehicleStrategy methods now hold the coupling to your Vehicle class or rather non-virtual methods somewhere. How does this solve that or am I misunderstanding you? Could you provide an example? Please and thanks – G.T.D. Sep 10 '17 at 05:07
  • @B1313 Nowhere in your example code you show derived classes calling the parent class. Eg. SUV calling methods on Vehicle. I assumed that all the child classes are doing are implementing abstract methods that are defined in the parent class, which then calls it. You should really make sure you example shows exactly what you are saying. As it is right now, the example is confusing. – Euphoric Sep 10 '17 at 08:54
1

I am a defender of 'composition over inheritance'. You are trying to write maintainable code right? In my experience, using only 'interfaces' and 'implementations' gives an extremely maintainable design. There is also a very good reason not to mix in inheritance: it makes the whole more complex. Complexity hurts maintainability because maintainability stems from simplicity among other things.

In your specific Vehicle example, I think a good course of action would be to extract the data from the code. What does that exactly entail? Set up some data source that contains all data on vehicles and the gearing system perhaps as well. The only implementation you are left with simply assumes one of the concrete cars by reading the data. Not only did you write less code this way, it became simpler by a few degrees. Second rework your design so that no Vehicle implementation requires specific methods. 'specific methods' violate LSP (the L of SOLID) and hurt overall maintainability in turn.

Now there is a slight drawback with this solution: it may hurt performance. Though that should not matter if you care about maintainability more, like me.

RabbitBones22
  • 151
  • 13
0

According to SOLID principles of OOP Design (and general OOP advice), you should code to an interface and never use inheritance

"Code to an interface" = is actually the "D" principle of SOLID, and it actually says that your modules should depend on abstractions, not concretions.

"Never use inheritance" = nonsense, since the "L" principle of SOLID actually guides you into how to use inheritance correctly.

Basically the car example that you gave is not good enough to describe what scenarios require inheritance, and what scenarios require implementations of interfaces.

In real life, you can state that Sedan "is a" Vehicle. In your software, you don't have an actual Sedan, you have a code representation of it. And then, you need to determine if the code representation of your Sedan "is a" code representation of your Vehicle. If it is, then you can safely use inheritance. It depends on you project needs, and is not prohibitted.

If you want to reuse code, and you don't have an explicit "is a" dependency between two classes, then you should use composition, you don't have to replicate any line of code.

To summarize, you can use all of these approaches, it depends on your needs and how you correctly model your objects. Moreover, please check all of the SOLID principles and make sure you understand all of them: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

Emerson Cardoso
  • 2,050
  • 7
  • 14
0

There is one thing that embarrasses me: your abstract class has properties that could be set by any child class. Why is this bad? There is a number of drawbacks, the most prominent and practical of which is the fragile base class problem. Put it shortly, any overridden method can do things that are not expected by the parent class. Hence, one of the most fundamental OOP principles is violated -- encapsulation. So my personal rule of thumb when using inheritance is that my child classes can not mutate parent's state.

So, your questions:

Am I misunderstanding the concept of "code to an interface and do not use inheritance" and/or the dependency inversion principle?

and

If I am not misunderstanding "code to an interface", dependency inversion, and not unaware of a better solution, and utilizing inheritance correctly then why would inheritance be considered {insert negative and/or demeaning adjective here}? Is there a downside(s) that I am not considering?

If your domain doesn't imply any other kinds of vehicles, then I think your design is fine (keeping in mind what I've written above). Right now your behavior resides where it should. Introducing service classes would make your design worse. Don't haste to introduce interfaces. First, in doesn't automatically guarantee that your domain decomposition is good. Second, remember the Rule of Three. People generally are bad at foreseeing the future. Third, decompose your domain first -- your software starts much earlier than you'll write the first line of code.

And on the whole terminology thing. Originally, inheritance was implemented as a mechanism for reuse. But the thing is that the implementation of inheritance is the same as the implementation of subtyping in all languages I'm aware of. And nevertheless they are almost always used as synonyms, the motivational forces for using one or another differed a lot.

Finally, implementation. I often find myself implementing a lot of methods in base abstract class. After that I start realizing that almost all of them can be moved to small value-objects. So that's how I would implement your code (sorry for php :) ):

abstract class Vehicle
{
    public function drive()
    {
        if ($this->readyToDrive()) {
            // absolutely the same for all kinds of vehicle
        }
    }

    abstract protected function readyToDrive();
}

class Sedan extends Vehicle
{
    private $gear;
    private $ignition;
    private $tank;
    private $seatBelts;

    public function __construct(IGear $gear, IIgnition $ignition, Itank $tank, ISeatBelts $seatBelts)
    {
        $this->gear = $gear;
        $this->ignition = $ignition;
        $this->tank = $tank;
        $this->seatBelts = $seatBelts;
    }

    protected function readyToDrive()
    {
        return $this->gear->isDrive() && $this->ignition->hasKey() && $this->tank->isEnoughFuel() && $this->seatBelts->areOn();
    }
}
Vadim Samokhin
  • 2,158
  • 1
  • 12
  • 17
  • Focusing on your example / mutation note: if you add an SUV class or Truck class then you have to copy and paste the construction section (including properties) to the new classes (which violates DRY, especially if you add another property like maxSpeed or whatever) (PHP avoids this?). I would see this as bad design because of the aforementioned. Since the base is abstract, why not just leave them in there? The derivatives will be the only instantiated versions, so the parent is irrelevant. Also, consumers of Vehicle have no consistent base. Why not just use private/protected setters instead? – G.T.D. Nov 14 '17 at 17:03
  • Firstly, there would be no logic duplication, just the same assignment statements, while DRY is about logic duplication. Secondly, even if initialization is the same at the moment, will it stay the same forever? It's just an implementation detail that happens to be the same at some moment. And thirdly, event without the first two points, I'd prefer my classes be autonomous, encapsulated and reliable instead of anything else. – Vadim Samokhin Nov 14 '17 at 17:17