19

The more I learn about different programming paradigms, such as functional programming, the more I begin to question the wisdom of OOP concepts like inheritance and polymorphism. I first learned about inheritance and polymorphism in school, and at the time polymorphism seemed like a wonderful way to write generic code that allowed for easy extensibility.

But in the face of duck typing (both dynamic and static) and functional features such as higher-order functions, I've begun to look at inheritance and polymorphism as imposing an unnecessary restriction based on a fragile set of relationships between objects. The general idea behind polymorphism is that you write a function once, and later you can add new functionality to your program without changing the original function - all you need to do is create another derived class which implements the necessary methods.

But this is a lot simpler to achieve through duck typing, whether it's in a dynamic language like Python, or a static language like C++.

As an example, consider the following Python function, followed by its static C++ equivalent:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

The OOP equivalent would be something like the following Java code:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

The major difference, of course, is that the Java version requires the creation of an interface or an inheritance hierarchy before it will work. The Java version thus involves more work, and is less flexible. Additionally, I find that most real-world inheritance hierarchies are somewhat unstable. We've all seen the contrived examples of Shapes and Animals, but in the real world, as business requirements change and new features are added, it's difficult to get any work done before you need to really stretch the "is-a" relationship between sub-classes, or else remodel/refactor your hierarchy to include further base-classes or interfaces in order to accommodate new requirements. With duck typing, you don't need to worry about modeling anything - you just worry about the functionality you need.

Yet, inheritance and polymorphism are so popular that I doubt it would be much of an exaggeration to call them the dominant strategy for extensibility and code reuse. So why are inheritance and polymorphism so wildly successful? Am I overlooking some serious advantages that inheritance/polymorphism have over duck typing?

4
  • I'm not a Python expert so I have to ask: what would happen if obj doesn't have a doSomething method? Is an exception raised? Does nothing happen? Apr 14, 2011 at 19:51
  • 6
    @Frustrated: A very clear, specific exception is raised.
    – dsimcha
    Apr 14, 2011 at 19:52
  • 11
    Isn't duck typing just polymorphism taken up to eleven? My guess is that you're not frustrated with OOP but with its statically typed incarnations. I can understand that, really, but that doesn't make OOP as a whole a bad idea.
    – user7043
    Apr 14, 2011 at 20:16
  • 3
    First read "widely" as "wildly"...
    – user8685
    Apr 14, 2011 at 21:20

10 Answers 10

23

I mostly agree with you, but for fun I'll play Devil's Advocate. Explicit interfaces give a single place to look for an explicitly, formally specified contract, telling you what a type is supposed to do. This can be important when you're not the only developer on a project.

Furthermore, these explicit interfaces can be implemented more efficiently than duck typing. A virtual function call has barely more overhead than a normal function call, except that it can't be inlined. Duck typing has substantial overhead. C++-style structural typing (using templates) can generate huge amounts of object file bloat (since each instantiation is independent at the object file level) and doesn't work when you need polymorphism at runtime, not compile time.

Bottom line: I agree that Java-style inheritance and polymorphism can be a PITA and alternatives should be used more often, but it still has its advantages.

31

Inheritance and polymorphism are widely used because they work, for certain kinds of programming problems.

It's not that they're widely taught in schools, that's backwards: they're widely taught in schools because people (aka the market) found that they worked better than the old tools, and so schools began teaching them. [Anecdote: when I was first learning OOP, it was extremely difficult to find a college that taught any OOP language. Ten years later, it was difficult to find a college that did not teach an OOP language.]

You said:

The general idea behind polymorphism is that you write a function once, and later you can add new functionality to your program without changing the original function - all you need to do is create another derived class which implements the necessary methods

I say:

No, It Isn't

What you describe is not polymorphism, but inheritance. No wonder you're having OOP problems! ;-)

Back up a step: polymorphism is a benefit of message-passing; it simply means that each object is free to respond to a message in its own way.

So...Duck Typing is (or rather, enables) polymorphism

The gist of your question seems to be not that you don't understand OOP or that you dislike it, but that you don't like defining interfaces. That's fine, and as long as you're careful things will work fine. The drawback is that if you've made a mistake - omitted a method, for example - you won't find out until run-time.

That's a static vs dynamic thing, which is a discussion as old as Lisp, and not limited to OOP.

2
  • 3
    +1 for "duck typing is polymorphism". Polymorphism and inheritance have been subconsciously chained together for far too long, and strict static typing is the only real reason they ever had to be.
    – cHao
    Dec 2, 2013 at 0:01
  • 1
    +1. I think the static vs dynamic distinction should be more than a side note - it's at the core of the distinction between duck typing and java-style polymorphism.
    – Sava B.
    Dec 12, 2017 at 0:08
8

I've begun to look at inheritance and polymorphism as imposing an unnecessary restriction based on a fragile set of relationships between objects.

Why?

Inheritance (with or without duck typing) assures reuse of a common feature. If it's common, you can assure that it's reused consistently in subclasses.

That's all. There's no "unnecessary restriction". It's a simplification.

Polymorphism, similarly, is what "duck typing" means. Same methods. Lots of classes with an identical interface, yet different implementations.

It's not a restriction. It's a simplification.

7

Inheritance is abused, but so is duck typing. Both can and do lead to problems.

With strong typing you get many "unit tests" done at compile time. With duck typing you often have to write them.

1
  • 3
    Your comment about testing only applies to dynamic duck typing. C++-style static duck typing (with templates) gives you errors at compile time. (Although, granted C++ template errors are pretty hard to decipher, but that's a different issue entirely.)
    – Channel72
    Apr 15, 2011 at 4:14
2

The good thing about learning things in school is that you do learn them. The not-so-good thing is you may accept them a little too dogmatically, without understanding when they are useful and when not.

Then, if you have learned it dogmatically, you may later "rebel" just as dogmatically in the other direction. That's not good either.

As with any such ideas, it's best to take a pragmatic approach. Develop an understanding of where they fit and where they don't. And ignore all the ways they've been oversold.

2

Yes, static typing and interfaces are restrictions. But everything since structured programming was invented (ie "goto considered harmful") has been about constraining us. Uncle Bob has an excellent take on this in his video blog.

Now, one can argue that constriction is bad, but on the other hand it brings order, control and familiarity to an otherwise very complex topic.

Relaxing constraints by (re-)introducing dynamic typing and even direct memory access is very powerful concept, but it can also make it harder for many programmers to deal with. Especially programmers used to relying on the compiler and type safety for much of their work.

1

Inheritance is a very strong relationship between two classes. I don't think Java has any stronger. Therefore, you should only use it when you mean it. Public inheritance is a "is-a" relationship, not a "usually is-a". It's really, really easy to overuse inheritance and wind up with a mess. In many cases, inheritance is used to represent "has-a" or "takes-functionality-from-a", and that's typically better done by composition.

Polymorphism is a direct consequence of the "is-a" relationship. If Derived inherits from Base, then every Derived "is-a" Base, and therefore you can use a Derived wherever you'd use a Base. If this doesn't make sense in an inheritance hierarchy, then the hierarchy is wrong and there's probably too much inheritance going on.

Duck typing is a neat feature, but the compiler won't warn you if you're going to misuse it. If you don't want to have to handle exceptions at run-time, you need to assure yourself that you're getting the right results every time. It may be easier just to define a static inheritance hierarchy.

I'm not a real fan of static typing (I consider it to often be a form of premature optimization), but it does eliminate some classes of errors, and many people think those classes are well worth eliminating.

If you like dynamic typing and duck typing better than static typing and defined inheritance hierarchies, that's fine. However, the Java way does have its advantages.

0

I've noticed that the more I use C#'s closures the less I'm doing traditional OOP. Inheritance used to be the only way to easily share implementation, so I think it was often over-used and the conception bounds were pushed too far.

While you can usually use closures to do most of what you'd do with inheritance, it can also get ugly.

Basically it's a right-tool-for-the-job situation: traditional OOP can work really well when you have a model that suits it, and closures can work really well when you don't.

-1

The truth lies somewhere in the middle. I like how C# 4.0 being statically typed language supports "duck typing" by "dynamic" keyword.

-1

Inheritance, even when reason from a FP perspective, is a great concept; it not only saves a lot of time, it gives meaning to the relation between certain objects in you program.

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Here the class GoldenRetriever has the same Sound as Dog for free thank's to inheritance.

I'll write the same example with my level of Haskell for you to see the difference

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

In here you don't escape having to specify sound for GoldenRetriever. The easiest thing in general would be to

sound GoldenRetriever = sound Dog

but just imagen if you have 20 functions! If there is a Haskell expert please show us an easier way.

That said, it would be great to have pattern matching and inheritance at the same time, where a function would default to the base class if the current instance doesn't have the implementation.

5
  • 6
    I swear, Animal is the anti-tutorial of OOP.
    – Telastyn
    Aug 11, 2014 at 20:01
  • @Telastyn I learned the example from Derek Banas on Youtube. Great teacher. In my opinion it is very easy to visualize and reason about. You are free to edit the post with a better example. Aug 11, 2014 at 20:08
  • this doesn't seem to add anything substantial over prior 10 answers
    – gnat
    Aug 11, 2014 at 20:37
  • @gnat While the "substance" is already out there, a person new to the subject might find an example easier to grasp. Aug 11, 2014 at 20:52
  • 2
    Animals and shapes are really bad applications of polymorphism for several reasons which have been discussed ad nauseum on this site. Basically, saying a cat "is an" animal is meaningless, since there is no concrete "animal." What you really mean to model is behavior, not identity. Defining an interface "CanSpeak" that could be implemented as a meow or a bark makes sense. Defining an interface "HasArea" that a square and circle can implement makes sense, but not a generic Shape.
    – user22815
    Aug 11, 2014 at 21:36

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