10

A colleague of mine came up with a rule-of-thumb for choosing between creating a base class or an interface.

He says:

Imagine every new method that you are about to implement. For each of them, consider this: will this method be implemented by more than one class in exactly this form, without any change? If the answer is "yes", create a base class. In every other situation, create an interface.

For example:

Consider the classes cat and dog, which extend the class mammal and have a single method pet(). We then add the class alligator, which doesn't extend anything and has a single method slither().

Now, we want to add an eat() method to all of them.

If the implementation of eat() method will be exactly the same for cat, dog and alligator, we should create a base class (let's say, animal), which implements this method.

However, if it's implementation in alligator differs in the slightest way, we should create an IEat interface and make mammal and alligator implement it.

He insists that this method covers all cases, but it seems like over-simplification to me.

Is it worth following this rule-of-thumb?

9
  • 27
    alligator's implementation of eat differs, of course, in that it accepts cat and dog as parameters. Commented Nov 12, 2013 at 20:13
  • 1
    Where you'd really like an abstract base class to share implementations, but ought to use interfaces for correct extensibility, you can often use traits instead. That is, if your language supports this.
    – amon
    Commented Nov 12, 2013 at 21:19
  • 2
    When you paint yourself into a corner, it's best to be in the one nearest the door.
    – JeffO
    Commented Nov 12, 2013 at 21:46
  • 10
    The biggest mistake that people make is believing that interfaces are simply empty abstract classes. An interface is a way for a programmer to say, "I don't care what you give me, as long as it follows this convention." Code reuse should then be accomplished (ideally) through composition. Your coworker is wrong.
    – riwalk
    Commented Nov 12, 2013 at 23:33
  • 2
    A CS proff of mine taught that superclasses should be is a and interfaces are acts like or is. So a dog is a mammal and acts like an eater. This would tell us that mammal should be a class and eater should be an interface. It has always been a very helpful guide. Sidenote: An example of is would be The cake is eatable or The book is writable. Commented Nov 13, 2013 at 0:07

12 Answers 12

13

I don't think that this is a good rule of thumb. If you are concerned about code reuse, you can implement a PetEatingBehavior which role is to define the eating functions for cats and dogs. Then you can have IEat and code reuse together.

These days I see less and less reasons to use inheritance. One big advantage is the ease of use. Take a GUI framework. A popular way to design an API for this is to expose a huge base class, and document which methods the user can override. So we can ignore all the other complex things our application needs to manage, since a "default" implementation is given by the base class. But we can still customize things by reimplementing the correct method when we need to.

If you stick to interface for your api, your user has usually more work to do to make simple examples work. But API with interfaces are often less coupled and easier to maintain for the simple reason that IEat contains less information than the Mammal base class. So consumers will depend on a weaker contract, you get more refactoring opportunities, etc.

To quote Rich Hickey: Simple != Easy

6
  • Could you provide a basic example of PetEatingBehavior implementation? Commented Nov 12, 2013 at 21:10
  • 1
    @exizt First, I am bad at choosing names. So PetEatingBehavior is probably the wrong one. I cannot give you an implementation since this is only a toy example. But I can describe refactoring steps: it has a constructor which takes all information used by cats/dogs to define the eat method (teeth instance, stomach instance, etc). It only contains the code common to cats and dogs used in their eat method. You simply have to create a PetEatingBehavior member and forward it calls to dog.eat/cat.eat. Commented Nov 12, 2013 at 21:23
  • I can't believe this is the only answer of many to steer the OP away from inheritances :( Inheritance is not for code re-use! Commented Nov 13, 2013 at 7:08
  • 1
    @DannyTuppeny Why not? Commented Nov 13, 2013 at 13:27
  • 4
    @exizt Inheriting from a class is a big deal; you're taking a (likely-permanent) dependency on something, which in many languages you can only have one of. Code re-use is a fairly trivial thing to use up this often-only base class on. What if you end up with methods where you want to share them with another class, but it already has a base class because of reusing other methods (or even, that it's part of a "real" hierarchy used polymorphically(?)). Use inheritance when ClassA is a type of ClassB (eg. a Car inherits from Vehicle), not when it shares some internal implementation details :-) Commented Nov 13, 2013 at 18:04
11

Is it worth following this rule-of-thumb?

It is a decent rule of thumb, but I know of quite a few places where I violate it.

To be fair, I use (abstract) base classes a lot more than my peers. I do this as defensive programming.

To me (in many languages), an abstract base class adds the key constraint that there may only be one base class. By choosing to use a base class rather than an interface, you're saying "this is the core functionality of the class (and any inheritor), and it is inappropriate to mix willy-nilly with other functionality". Interfaces are the opposite: "This is some trait of an object, not necessarily its core responsibility".

These sort of design choices create implicit guides to your implementors about how they should use your code, and by extension write their code. Because of their greater impact to the codebase, I tend to take these things into consideration more strongly when making the design decision.

1
  • 1
    Rereading this answer 3 years later, I find this to be a great point: By choosing to use a base class rather than an interface, you're saying "this is the core functionality of the class (and any inheritor), and it is inappropriate to mix willy-nilly with other functionality" Commented Jan 3, 2016 at 15:07
9

The problem with your friend's simplification is that determining whether or not a method will ever need to change is exceptionally difficult. It is better to use a rule that speaks to the mentality behind superclasses and interfaces.

Instead of figuring out what you should do by looking to what you think the functionality is, look to the hierarchy you are trying to create.

Here is how a professor of mine taught the difference:

Superclasses should be is a and interfaces are acts like or is. So a dog is a mammal and acts like an eater. This would tell us that mammal should be a class and eater should be an interface. An example of is would be The cake is eatable or The book is writable (making both eatable and writable interfaces).

Using a methodology like this is reasonably simple and easy, and causes you to code to the structure and concepts rather than to what you think the code will do. This makes maintenance easier, the code more readable, and the design simpler to create.

Regardless of what the the code might actually say, if you use a method like this you could come back five years from now and use the same method to retrace your steps and easily figure out how your program was designed and how it interacts.

Just my two cents.

6

At the request of exizt, I'm expanding my comment to a longer answer.

The biggest mistake that people make is believing that interfaces are simply empty abstract classes. An interface is a way for a programmer to say, "I don't care what you give me, as long as it follows this convention."

The .NET library serves as a wonderful example. For example, when you write a function that accepts an IEnumerable<T>, what you are saying is, "I don't care how you are storing your data. I just want to know that I can use a foreach loop.

This leads to some very flexible code. Suddenly in order to be integrated, all you need to do is play by the rules of the existing interfaces. If implementing the interface is difficult or confusing, then maybe that is a hint that you are trying to shove a square peg in a round hole.

But then the question comes up: "What about code reuse? My CS professors told me that inheritance was the solution to all code reuse problems and that inheritance allows you to write once and use everywhere and it would cure menangitis in the process of rescuing orphans from the rising seas and there would be no more tears and on and on etc. etc. etc."

Using inheritance just because you like the sound of the words "code reuse" is a pretty bad idea. The code styling guide by Google makes this point fairly concisely:

Composition is often more appropriate than inheritance. ... [B]ecause the code implementing a sub-class is spread between the base and the sub-class, it can be more difficult to understand an implementation. The sub-class cannot override functions that are not virtual, so the sub-class cannot change implementation.

To illustrate why inheritance is not always the answer, I'm going to use a class called MySpecialFileWriter†. A person who blindly believes that inheritance is the solution to all problems would argue that you should try to inherit from FileStream, lest you duplicate FileStream's code. Smart people recognize that this is stupid. You should just have a FileStream object in your class (either as a local or member variable) and use its functionality.

The FileStream example may seem contrived, but it's not. If you have two classes that both implement the same interface in the exact same way, then you should have a third class that encapsulates whatever operation is duplicated. Your goal should be to write classes that are self-contained reusable blocks that can be put together like legos.

This doesn't mean that inheritance should be avoided at all costs. There are many points to consider, and most will be covered by researching the question, "Composition vs. Inheritance." Our very own Stack Overflow has a few good answers on the subject.

At the end of the day, your coworker's sentiments lack the depth or understanding necessary to make an informed decision. Research the subject and figure it out for yourself.

† When illustrating inheritance, everyone uses animals. That is useless. In 11 years of development, I've never written a class named Cat, so I'm not going to use it as an example.

2
  • So, if I understand you correctly, dependency injection provides the same code reuse capabilities as inheritance. But what would you use inheritance for, then? Would you provide a use-case? (I really appreciated the one with FileStream) Commented Nov 14, 2013 at 12:58
  • 1
    @exizt, no it does not provide the same code reuse capabilities. It provides better code reuse capabilities (in many cases) as it keeps implementations well contained. Classes are interacted with explicitly through their objects and their public API, rather than implicitly gaining capabilities and completely changing half of the functionality by overloading existing functions.
    – riwalk
    Commented Nov 14, 2013 at 16:17
4

Examples rarely make a point, especially not when they are about cars or animals. An animal's way of eating (or processing food) is not really tied to its inheritance, there is no single way of eating that would apply to all animals. This is more dependent on its physical capabilities (the ways of input, processing and output so to say). Mammals can be carnivores, herbivores or omnivores for example, while birds, mammals and fish can eat insects. This behavior can be captured with certain patterns.

You're in this case better off by abstracting behavior, like this:

public interface IFoodEater
{
    void Eat(IFood food);
}

public class Animal : IFoodEater
{
    private IFoodProcessor _foodProcessor;
    public Animal(IFoodProcessor foodProcessor)
    {
        _foodProcessor = foodProcessor;
    }

    public void Eat(IFood food)
    {
        _foodProcessor.Process(food);
    }
}

Or implement a visitor pattern where a FoodProcessor tries to feed compatible animals (ICarnivore : IFoodEater, MeatProcessingVisitor). The thought of live animals may hinder you in thinking about ripping their digestive track out and replacing it with a generic one, but you're really doing them a favor.

This will make your animal a base class, and even better, one you'll never have to change again when adding a new type of food and an according processor, so you can focus on the things that make this specific animal class you're working on so unique.

So yes, base classes have their place, the rule of thumb does apply (often, not always, as it is a rule of thumb) but keep looking for behavior you can isolate.

3
  • 1
    The IFoodEater is kind of redundant. What else do you expect to be able to eat food? Yeah, animals as examples are terrible thing.
    – Euphoric
    Commented Nov 12, 2013 at 20:48
  • 1
    What about carnivorous plants? :) Seriously, one major issue with not using interfaces in this case is that you've intimately tied the concept of eating with Animals and it's a lot more work to introduce new abstractions for which the Animal base class is not appropriate. Commented Nov 13, 2013 at 17:24
  • 1
    I believe "eating" is the wrong idea here: go more abstract. How about an IConsumer interface. Carnivores can consume meat, herbivores consume plants, and plants consume sunlight and water.
    – user22815
    Commented Nov 20, 2013 at 2:07
2

An interface is really a contract. There is no functionality. It is up to the implementor to implement. There is no choice as one must implement the contract.

Abstract classes give implementors choices. One can choose to have a method that everyone must implement, provide a method with base functionality that one could override, or provide some base functionality that all inheritors can use.

For example:

public abstract class Person
    {
        /// <summary>
        /// Inheritors must implement a hello
        /// </summary>
        /// <returns>Hello</returns>
        public abstract string SayHello();
        /// <summary>
        /// Inheritors can use base functionality, or override
        /// </summary>
        /// <returns></returns>
        public virtual string SayGoodBye()
        {
            return "Good Bye";
        }
        /// <summary>
        /// Base Functionality that is inherited 
        /// </summary>
        public void DoSomething()
        {
            //Do Something Here
        }
    }

I would say your rule of thumb could be handled by a base class as well. In particular since many mammals probably "eat" the same then using a base class may be better than an interface, since one could implement a virtual eat method that most inheritors could use and then the exceptional cases could override.

Now if the defination of "eating" varies widely from animal to animal then perhaps and interface would be better. This is sometimes a gray area and depends on the individual situation.

1

No.

Interfaces are about how the methods are implemented. If you are basing your decision of whenever to use interface on how the methods will be implemented, then you are doing something wrong.

For me, difference between interface and base class is about where the requirements stand. You create interface, when some piece of code requires class with specific API and implied behavior that emerges from this API. You cannot create an interface without code that will actually call it.

On the other side, base classes are usually meant as way to reuse code, so you rarely bother with code that will call this base class and only take into account the hiearchy of objects that this base class is part of.

2
  • Wait, why re-implement eat() in every animal? We can make mammal implement it, can't we? I understand your point about the requirements, though. Commented Nov 12, 2013 at 20:20
  • @exizt : Sorry, I read your question incorrectly.
    – Euphoric
    Commented Nov 12, 2013 at 20:42
1

It's not really a good rule of thumb because if you want different implementations you could always have an abstract base class with an abstract method. Every inheritor is guaranteed to have that method, but each defines their own implementation. Technically you could have an abstract class with nothing but a set of abstract methods, and it'd be basically the same as an interface.

Base classes are useful when you want an actual implementation of some state or behavior that can be used across several classes. If you find yourself with several classes that have identical fields and methods with identical implementations, you can probably refactor that into a base class. You just have to be careful in how you inherit. Every existing field or method in the base class must make sense in the inheritor, especially when it is cast as the base class.

Interfaces are useful when you need to interact with a set of classes in the same way, but you can't predict or don't care about their actual implementation. Take for example something like a Collection interface. Lord only knows all the myriad ways someone might decide to implement a collection of items. Linked list? Array list? Stack? Queue? This SearchAndReplace method doesn't care, it just needs to be passed something that it can call Add, Remove, and GetEnumerator on.

1

I'm sort of watching the answers to this question myself, to see what other people have to say, but I will say three things that I'm inclined to think about this:

  1. People put way too much faith into interfaces, and too little faith into classes. Interfaces are essentially very watered down base classes, and there are occasions when using something lightweight can lead to things like excessive boilerplate code.

  2. There is nothing wrong at all with overriding a method, calling super.method() within it, and letting the rest of its body just do things that don't interfere with the base class. This does not violate the Liskov Substitution Principle.

  3. As a rule of thumb, being this rigid and dogmatic in software engineering is a bad idea, even if something generally is a best practice.

So I would take what was said with a grain of salt.

2
  • 5
    People put way too much faith into interfaces, and too little faith into classes. Funny. I tend to think that the opposite is true. Commented Nov 12, 2013 at 20:43
  • 1
    "This does not violate the Liskov Substitution Principle." But it is extremely close to become a violation of LSP. Better be safe than sorry.
    – Euphoric
    Commented Nov 12, 2013 at 20:49
1

I want to take a lingual and semantic approach towards this. An interface is not there for DRY. While it can be used as a mechanism for centralization alongside having an abstract class that implements it, it's not meant for DRY.

An interface is a contract.

IAnimal interface is a all-or-nothing contract between alligator, cat, dog and the notion of an animal. What it says essentially is "if you want to be an animal, either you must have all these attributes and characteristics, or you're not an animal anymore".

Inheritance on the other side is a mechanism for reuse. In this case, I think having a good semantic reasoning can help you decide which method should go where. For example, while all animals might sleep, and sleep the same, I won't use a base class for it. I first put the Sleep() method in the IAnimal interface as an emphasis (semantic) for what it takes to be an animal, then I use a base abstract class for cat, dog, and alligator as a programming technique to don't repeat myself.

0

I'm not sure this is the best rule of thumb out there. You can put an abstract method in the base class and have each class implement it. Since every mammal has to eat it would make sense to do that. Then each mammal can use its one eat method. I typically only use interfaces for communication between objects, or as a marker to mark a class as something. I think overusing interfaces makes for sloppy code.

I also agree with @Panzercrisis that a rule of thumb should just be a general guideline. Not something that should written in stone. There undoubtedly will be times where it just doesn't work.

-1

Interfaces are types. They provide no implementation. They simple define the contract for handling that type. This contract has to be met by any class implementing the interface.

The use of interfaces and abstract/base classes should be determined by the design requirements.

A single class does not require an interface.

If two classes are interchangeable within a function they should implement a common interface. Any functionality that is implemented identically could then be refactored into an abstract class that implements the interface. The interface could be deemed redundant at that point if there is no distinguishing functionality. But then what is the difference between the two classes?

Using abstract classes as types is generally messy as more functionality is added and as a hierarchy expands.

Favour composition over inheritance - all this goes away.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.