2
$\begingroup$

When a student is first learning about class inheritance it is nice to use an example from every-day life. For example, Plastic Cups are Cups and GlassCups are also Cups.

class Cup:   
    pass

class PlasticCup(Cup):
    pass  

class PaperCup(Cup):
    pass  

class RoughHewnWoodenCup(Cup):
    pass  

class Glass(Cup):
    pass  

I am aware that interface classes exist, wrapper classes exist, etc...

I think that when dealing with new students the principle of inheritance is best explained with analogies to life outside of writing code.

For example, PlasticCups are Cups, so PlasticCups inherit from the Cup class.

I have an example using Cups but my questions is,

What Examples of class inhertance can you provide which do not talk about drinking vessels for beverages such as water or fruit juice?

Your answer need not use python-style syntax. Use whatever syntax you prefer.

$\endgroup$
1

5 Answers 5

8
$\begingroup$

I think that when dealing with new students the principle of inheritance is best explained with analogies to life outside of writing code.

I'm not so sure. I prefer to use application-centric examples because the connection between the example and an actual program that students care about is clear.

Students are likely never going to write a program dealing with cups, cars or animals or most of the other examples that are typically brought out in courses, leading to a "why should I care?" effect.

I prefer to simply jump right into the actual use case for inheritance. If there is no immediate use case, then I'd try to invent one that motivates a situation where it's awkward not to use inheritance, then show how inheritance solves the problem elegantly. If I can't, then it's probably not useful to teach inheritance at this point (assuming I have control over the curriculum).

For example, if the course deals with back end programming, perhaps a User class with a Moderator subclass which is a User with the ability to delete comments would be a useful motivation. Another User could be UnconfirmedUser which needs email confirmation to become a User. Since just about everyone is familiar with signing up for apps and posting in comment threads (feel free to call the activity "Reddit comments" or similar), the example will immediately connect to an app the students can grasp and realize.

For a course that involves game programming, creating a tree of Entity classes that have Player and Enemy (with further subclasses for different types of enemies) could neatly illustrate avoiding repetition and using polymorphism to loop over the Entities and .render() each one regardless of its type. If you don't have easy access to graphics, this could be part of a text adventure game.

Also in the graphics domain could be a Shape superclass with derived Rectangle, Triangle, Square, etc (with Square being derived from a Rectangle). Most languages have lightweight graphics libraries (often turtle graphics) to enable your students to draw these shapes. Seeing them on the screen is much more concrete than printing out properties of cups or animals.

If you're totally set on "real life" (exists physically) over "real world" (exists in code people actually write), there are still many options that cover both: e-commerce Products with derived categories, Employee classes with Manager, Intern subclasses (a bit boring and enterprisey), a Minecraft-related inventory system Item or Mineral which maps to physical items, etc.

It's a good instinct to be practical, but keeping one foot firmly in the computer application world keeps the gap bridged and avoids contrivance.

$\endgroup$
8
  • $\begingroup$ His question is basically the same as mine, so maybe you can provide an answer for that one too (thanks in advance!) cseducators.stackexchange.com/questions/6709/… $\endgroup$ Mar 31 at 9:14
  • $\begingroup$ @Qiulang邱朗 thanks for the response--yes, there are similarities, but I think they're separate questions. Your definition of "real world" is "used in a serious programming project" whereas this poster means "something from everyday life". I think both are potentially problematic so I stick to my answer. Different approaches but same problem--students probably don't care about paths or Python implementation details yet. It's too far away from the sort of fun apps they want to build. If I had to choose, though, I'd take that over the "car/animal"-type examples, although it depends on audience. $\endgroup$
    – ggorlen
    Mar 31 at 13:25
  • 1
    $\begingroup$ Also, I've heard that young people tend not to understand how filesystems work, so given the dominance of phones, it's possible that the Python path example will be lost on many students. That can't happen with comments or users which are closer to the user space than the developer space. $\endgroup$
    – ggorlen
    Mar 31 at 13:26
  • $\begingroup$ @ggorlen My statement, "I think that when dealing with new students the principle of inheritance is best explained with analogies to life outside of writing code" was overly opinionated. It might be best to pretend that a student asked, "In something other than python or java-script or code, what real-world thing behaves is sort of like class inheritance?" $\endgroup$ May 25 at 21:43
  • $\begingroup$ @ggorlen when dealing with new students learning German, if you define German words in terms of other German words, you will be unable to teach much of the German language to the student. Why not teach German by explaining German vocabulary in terms of something outside of the German language. Haus: ein Ort zum Schlafen, Essen, Baden und zum Aufbewahren von Hab und Gut If you do not understand any of the words used to define the word Haus you won't learn German. Likewise, you cannot learn python from python or java-script from java-script. $\endgroup$ May 25 at 21:43
4
$\begingroup$

This isn't as clear cut as your question would have it be. There are several cases where a solution, proposed to be solved by inheritance, could be solved in different ways. For example, your paper/glass cup example is prone to arguments that the material should be a property of the class, not a derivation of the class itself.

Don't do this:

I need the perfect example so that I can demonstrate all of the inheritance use cases.

Do this instead:

I want to showcase this particular behavior. What example would work well to make the behavior seem intuitive?

Instead of looking for the right example, first consider what you're trying to demonstrate. Usually, this will be a particular behavior (in this case of inheritance). This will help you identify in shaping an example that makes the intended demonstration seem like the most intuitive solution to the problem.

For example, I could focus on polymorphism, specifically the ability to implicitly cast to a base class when passing derived instances into methods. The behavior I'm trying to demonstrate would be something like:

MethodA only accepts a DerivedA.
MethodB only accepts a DerivedB.
MethodC accepts any kind of Base such as DerivedA or DerivedB.

To put it plainly: some methods are picky, others are not. I can now think of an example that is very similar to this.

Carnivores only eat meat.
Herbivores only eat plants.
Omnivores eat any kind of food, such as meat or plants.

And there you have it, an example that intuitively already matches the kind of behavior that I'm trying to demonstrate.

public class Food {}

public class Meat : Food {}
public class Steak : Meat {}
public class Drumstick : Meat {}

public class Plant : Food {}
public class Grass : Plant {}
public class Leaves : Plant {}

public class Carnivore
{
    public void Eat(Meat meat) {}
}

public class Herbivore
{
    public void Eat(Plant plant) {}
}

public class Omnivore
{
    public void Eat(Food food) {}
}

And its usage:

var steak = new Steak();
var drumstick = new Drumstick();
var grass = new Grass();
var leaves = new Leaves();

var cat = new Carnivore();
cat.Eat(steak);
cat.Eat(drumstick);
cat.Eat(grass);             // Compiler error - not a Meat!
cat.Eat(leaves);            // Compiler error - not a Meat!

var cow = new Herbivore();
cow.Eat(steak);             // Compiler error - not a Plant!
cow.Eat(drumstick);         // Compiler error - not a Plant!
cow.Eat(grass);             
cow.Eat(leaves);            

var human = new Omnivore();
human.Eat(steak);             
human.Eat(drumstick);         
human.Eat(grass);             
human.Eat(leaves);  

Is it a perfectly realistic example? No. Not every herbivore eats leaves and grass. Humans don't eat any and all plant products either. But it is the nature of examples that they are simplistic representations. They are simplistic by nature because it makes them easier to digest for students who are being introduced to something new.

To summarize
Tailor your examples to what you're specifically trying to teach. Don't try to pursue the perfect example to be used in all use cases.
By maximizing the relevance of the specific example, you minimize the student's required acceptance of what you're trying to showcase.

Avoid reusing the same examples across multiple behaviors, unless you are absolutely certain that the example is equally intuitive for both cases.

$\endgroup$
2
$\begingroup$

It depends on the goal. For an initial, highly intuitive brush, I've frequently used animals. They share some advantages over cups.

  1. There are a lot of them.
  2. They already come pre-categorized into class trees thanks to hundreds of years of labor by thousands of people.
  3. It's not just a good example, it is quite real. Animals utilize genuine inheritance. Racoons are able to produce bile for the same reason that we are, because we inherited the DNA for a gallbladder from our common ancestors, and the behaviors of these organs is essentially the same for both species.1
  4. Animals don't just do well to explain inheritance, but lend themselves towards a lot more advanced thinking.

They clearly lend themselves to inheritance well, because they are genuine manifestations of it in nature. (All mammals growHair(), but along the tree, rodentia's approach would implement that as fur, and the naked mole rat would have to override that yet again.)

At this point it's worth noting that I do not advocate sticking to only this example. Once it has been made intuitive, it's time to move on and have students try it in other contexts.

The example also readily (happily!) switches over to teaching abstract classes once students are ready. (Earlier we examined a fictional animal system. But there was a bit of a problem before that we didn't delve into. Let's look at this now: I can instantiate a poecilia latipina, but what would it mean to instantiate a fish?? That shouldn't be possible! "Fish" isn't a specific fish, it's an abstract idea of fish...)

It also lends itself to interfaces. (Earlier, we looked at a fictional animal inheritance system. It had another problem! We might start with the idea that birds fly, but what of penguins? Or bats? We need a new tool for this action.)

If you want them to think deeply about OOP (after they've learned the pieces of it well) have them consider carcinization. How would something like that be represented in OOP? To my taste, there is no fixed answer, but it's an interesting question and lends itself to deeper thinking about both OOP and biology.

#Bad Coding Practice in the Animal Kingdom#

There was an objection in the comments section, pointing out that it is bad practice to have a child's interface become larger than the parent interface. I would strenuously argue that, while this is true, we are still duty-bound to explain what inheritance can do, and in languages like Java and C++, we are able to expand the interface. We must make the mechanics of this as intuitive as the rest.

It's also worth noting that all of this animal stuff probably takes no more than 15 (cumulative!) minutes over three different days. I also heavily lean into good coding practice in my courses, but I do it in many passes.

This is an example from nature. It does not follow good coding style, but that's not necessarily a flaw. It is perfectly reasonable to revisit the animal example later on and talk about why we might not want to allow children to expand interfaces in our own programming projects.

1 You may notice an apparent difference between biological and OOP inheritance in that due to limited lifespan, we are not literally handing our bilirubin to our ancestor in order to make the bile - we instead inherit the mechanism to do it ourselves. However, I would argue that this is worth mentioning, but not a difference that you would want to lean into too hard in the classroom, as this passing of information up and down the hierarchy breaks OOP's metaphor. We are not meant to even be aware of such cross-generational passing when we use an object in OOP. It is only important briefly when coding the object itself.

$\endgroup$
1
0
$\begingroup$

Real world examples are good, before you get to coding. Use them first to explain the idea. Then introduce a coding problem: How can we use this idea to simplify this code.

Some languages prevent the most powerful use of this idea. E.g Java only allows multiple inheritance of interfaces. Eiffel, Python (it's a duck), and Rust, don't have this problem.

$\endgroup$
6
  • $\begingroup$ I am aware that python uses duck typing, but I am confused why you wrote something like, "Python (it is a duck)". The Python programming language uses duck typing, but python itself is not a duck-type. An example of duck typing would be a class object which emulates the list class by defining all of the following attributes: append, clear, copy, count, extend, index, insert, pop, remove, reverse, sort $\endgroup$ Mar 29 at 23:22
  • $\begingroup$ @SamuelMuldoon I did not mean to be that literal. $\endgroup$ Apr 4 at 10:08
  • $\begingroup$ I agree that real-world examples are useful before students get into writing syntactically worry code in order to explain the idea of inheritance. The question is, what is a specific example of a real-world example? Maybe souring powder and light-bulbs inherit from inedible items? I am not sure that is an example students would find interesting. $\endgroup$ May 25 at 21:48
  • $\begingroup$ @SamuelMuldoon python can quack, so it is a duck (according to duck-typing). $\endgroup$ May 27 at 12:07
  • $\begingroup$ @SamuelMuldoon a class does not implement the set of things that are not in another set. E.g. you can't make a class not edible. You could, but what methods would it have? don't eat? $\endgroup$ May 27 at 12:09
0
$\begingroup$

This is a harder question than it seems if you want to get across the idea that a type hierarchy should contain variations but with the same interface. If the interface gets extended as you go down the hierarchy then you have the problem that you too-often need to do type casting of variables (Java) or risk runtime failure (Python) if you make certain assignments and then try to message the referenced object.

In particular, I try to get this idea across early, since if it isn't taught and the opposite process is, it can be very hard to unlearn bad (IMO) habits and replace them. It was for me in fact.

It is the same problem you get using too many if-statements and the program becomes so ad-hoc that it is difficult to understand, much less modify.

Your example of cups is pretty good, I think, since cups should all be able to have the same usages (think UI). So, I'd try to think about things in real life that are basically alike, but have variations.

Clothing fits that in some ways. There are many variations on "skirt" or "pants" or "jacket" that all interface to the world in the same way. They have color, closure methods, sizes, etc. but a visit to a clothing store will give you a sense of the variations.

I'll also note that all of the keys on my current keyboard have the same "interface" but all perform a different task.

Smartphones are pretty generic, actually, in spite of the marketing hype. Mine is probably a lot like yours.

Vehicle is a poor choice unless you restrict things greatly. Buses and cars seem to need different interfaces. But, within a narrow range, it might be made to work. If you ask for the "fare" for buses, but not cars then you get in to trouble.

Note that I try to build software, and teach how to build it, so that objects can be responsible for their own actions and when I ask an object to do something it should be able to respond without me knowing (or specifying) its precise implementation.

So, I find problems when the interface and the implementation are too tightly bound together. When I need to do such things. I'd rather extend the interface explicitly (Java, again) and then implement the new interface than simply add public methods to something with a more general interface. Then, (more Java), you can let variables have interface types, rather than concrete types.

FWIW, I can't always accomplish that, but it is a goal and it results in better code.

As I said in a comment to another question, the Linnaeus hierarchy of the animal kingdom consists entirely of abstract "classes" except at the leaves of the structure. I think that is important to keep in mind.

$\endgroup$

Your Answer

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

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