Object-Orientation - Inheritance & Polymorphism (in Java)
The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
–Joe Armstrong, creator of Erlang, a functional programming language
- Overview
- Basic Implementation
- Multi-Level Inheritance
- Polymorphism
- Similarity & Difference
- Inheritance vs. Composition
- Is Inheritance Evil?
- Conclusions
Overview
Concept
Inheritance is a mechanism for basing one object or a class on another object or class when the two objects or classes include similar implementations of their behaviors.
- Properties and methods of one object or class are automatically “passed down” to the objects or classes that inherit from it.
Historical tangent
Inheritance was first implemented in Norway in 1969 for Simula, considered the first fully object-oriented programming language. Simula included now-standard, pedestrian, object-oriented features such as:
-
Objects
-
Classes
-
Constructors
-
Inheritance
-
Garbage collection
Think about that next time you hear someone say technology develops at lightning-speed rates of innovation.
Benefits
The touted benefits of inheritance include:
-
Code reuse
-
Less code redundancy
-
Easier code maintenance
-
Conceptual clarity
Basic Implementation
Abstract Example
Imagine we had the following class, A
with just a single property, message
, and its getter
and setter
methods.
public class A {
// no-args constructor
public A() {
System.out.println( "A new A object is born!" );
this.message = "Hello!";
}
// just one property to keep it simple
private String message;
// getter + setter methods
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
if (msg.length > 0) this.message = message;
}
}
Abstract Example (continued)
Another class, B
, could inherit the properties and methods of A
by using the extends
keyword.
public class B extends A {
}
- A
B
object could be instantiated with no further code and it would automatically inherit the public interface defined byA
.
B bObj = new B();
System.out.println( bObj.getMessage() );
Abstract Example (continued again)
We often create UML diagrams indicating class relationships, where the arrow points from parent to child.
- In almost all cases, the child class would contain its own unique additional properties and methods that make it different in some way from the parent… we have kept class
B
blank just to focus on the inherited properties and methods.
Abstract Example (continued once more)
When this program is run, two lines are output:
A new A object is born!
Hello!
-
The first line above, ‘A new A object is born!’ is output by the
A
class’s constructor function, which our code does not explicitly call. -
All classes, including our
B
class, are given a default no-args constructor by Java if no other constructor is defined within them. -
This no-args constructor automatically calls super().
-
If we were to write this constructor for
B
ourselves, it would look like:
public B() {
super();
}
- The super keyword is a reference to the code in the parent class.
Abstract Example (continued the final time)
The second line output above, ‘Hello!’, is a result of the call to the B
object’s getMessage()
method.
-
This method, like all other public properties and methods, is inherited from the parent class,
A
, into the child class,B
. -
Private properties and methods are not inherited by a child class, but can be accessed through public getter and setter methods.
Visibility
By definition, the private property, message
, of the A
class is not visible from the B
class.
- Yet, the
A
class’s getter for that property,getMessage()
, is public and thus is visible from within theB
class.
Multi-Level Inheritance
Concept
We can continue our example by giving B
its own child, C
.
public class C extends B {
// a no-args constructor
public C() {
super(); // call B's no-args constructor
}
// an overloaded constructor that accepts a message
public C( String message) {
super(); // call B's no-args constructor
System.out.println( "A new C object is born!" );
this.setMessage( message );
}
}
Concept (continued)
A UML diagram showing multi-class inheritance.
- As with
B
, we would normally add some unique public methods to child classes likeC
that make them behave differently from their parent, but we are not doing that here just to focus on the inherited components.
Analysis
C
is now a child of B
, which itself is a child of A
.
-
All the public properties and methods of
A
are inherited byB
. -
All the public properties and methods of
B
, including those inherited fromA
, are inherited byC
. -
Thus,
C
implements the public interface of bothA
andB
. -
What would the following code output?
C cObj = new C( "Welcome!!" );
System.out.println( cObj.getMessage() );
Polymorphism
Concept
An object or class can assume more than one ‘shape’.
-
In our example, a
C
object implements the public interfaces ofC
,B
, and anA
. -
We could say that our
C
object is aC
object, as well as aB
object, and anA
object. -
It’s possible to verify this in code using the
instanceof
operator.
if (cObj instanceof C && cObj instanceof B && cObj instanceof A) {
System.out.println("It's a C, B, and A object all-in-one!");
}
Application
Polymorphism can be useful when we want to store a bunch of related objects of different-but-related types into an array or other grouping data structure in order to perform some kind of batch operation on them.
A[] myObjs = {
new A(),
new B(),
new C("Welcome!!")
};
for (A myObj : myObjs) {
String message = myObj.getMessage();
System.out.println(message);
}
Overriding
It is often desireable to have child classes implement the instance methods defined in the parent class somewhat differently.
-
A child class can define methods with the same signatures as the instance methods in the parent class - this is called overriding.
-
For example, each of our
A
,B
, andC
classes could contain different implementations of thegetMessage()
method. -
It is possible for a child class’s overridden method to call the parent class’s version of that same method.
-
A child class can call any public method as-defined in its parent class’s code by using the keyword super, e.g.
super.getMessage();
Similarity & Difference
Concept
In real applications, child classes almost invariably do something different from their parent classes, whether by overriding some parent methods or implementing their own unique methods that are not present in the parent. This difference gives the child class a reaason to exist.
- We’ll take a look at the similarity and difference in two simple classes we’ll call
A
andB
.
Example classes
The parent class:
public class A {
public void doSomething() {
System.out.println("An A object doing something.");
}
}
The child class:
public class B extends A {
public void doSomethingDifferent() {
System.out.println("A B object doing something *different*.");
}
}
Similarity
Since B
inherits from A
, both classes encapsulate A
’s doSomething()
method.
doSomething()
can be called on anyA
object, since it is defined within theA
class.
A myA = new A();
myA.doSomething();
- and it can be called on any
B
object, sinceB
objects inherit it.
B myB = new B();
myB.doSomething();
Similarity (continued)
Both A
and B
objects can be considered A
objects, since B
implements A
’s public interface. So we can take advantage of polymorphism to perform a batch operation on both objects.
// instantiate an A and a B object
A myA = new A();
B myB = new B();
// put them both into an A-typed array
A[] myObjects = { myA, myB };
// loop through all objects in array
for (A myObj : myObjects) {
myObj.doSomething(); // works fine, since they both have this method
}
Difference
B
objects, of course, have their own unique doSomethingDifferent()
method that A
objects do not have.
- In a polymorphic context, we can perform a batch operation on both objects using a loop, as done previously, but also have
B
objects do their own unique behavior, if desired, by using theinstanceof
operator.
// loop through all objects in array
for (A myObj : myObjects) {
myObj.doSomething(); // works fine, since they both have this method from A
// have any B-type objects do their special behavior...
if (myObj instanceof B) {
// first cast to B-type
B sameObjsAsBType = (B) myObj;
// now call B-type's unique method
sameObjsAsBType.doSomethingDifferent();
}
}
Inheritance vs. Composition
In context
The goals of inheritance are to reuse code, reduce redundancy, and provide conceptual clarity in code.
-
A child class can be based on, and inherit all public properties and methods of, a parent class.
-
Composition, in contrast is the practice of storing an object of one type as a property of an object of another type.
-
Composition can be useful when the two objects are do not share method implementation code in common, but one of the objects nevertheless benefits from being able to access some methods of the other.
Example
For example, when using the Processing animation framework, the PApplet class contains lots of useful animation-related public properties and methods.
- To use that framework, we want one of our classes to inherit those useful functions and thus represent the app window.
public class App extends PApplet { ... }
-
App
objects now will havePApplet
behaviors, such as popping open a new animation window, drawing ellipses, detecting mouse clicks and key presses, having itsdraw()
method called 60 times per second, etc. -
In our animations we may want to have donkeys floating across the screen, so we might create a
Donkey
class to encapsulate all things related to donkeys - their width, height, x position, y position, etc.
public class Donkey { ... }
Example (continued)
Our Donkey
objects don’t need to pop open windows, draw ellipses, detect mouse clicks, key presses, or do the other things that the PApplet
class can do. Our Donkey
class represents donkeys, not app windows.
-
But our
Donkey
objects will want to make themselves appear within a PApplet window once in a while using just PApplet’simage()
method. -
This is a good candidate for composition, not inheritance - the vast majority of the
PApplet
code is unrelated and undesireable toDonkey
.
public class Donkey {
private App app; // will hold an App object, which is our child class of PApplet
public Donkey(App app) {
this.app = app; //store the App object composed within this Donkey
}
public void drawMeToScreen() {
this.app.image( ... ); // use the App object's image method to draw to screen
}
// ...
Example (continued again)
The App class can instantiate a Donkey object and pass itself to its constructor for the Donkey to hold a reference to.
public class App extends PApplet {
private Donkey myDonkey; // will hold a Donkey object
public void setup() {
// pass this App object as an argument to the Donkey constructor
this.myDonkey = new Donkey(this); // the Donkey will now have a reference to the App object
}
public void draw() {
// the donkey can now draw itself to the screen
this.myDonkey.drawMeToScreen();
}
}
- We would normally use a
setter
to set the value of themyDonkey
property, but have simplified this code in order to focus on the conceptual point.
Criticisms of Inheritance
Concept
There are certainly those who criticise object-oriented progrmaming, and some specifically who disapprove of Java’s inheritance model.
-
The main criticism rests upon Java’s inability to handle inheritance from multiple classes.
-
This inability can lead to convoluted and undesireable code.
Example scenario
Take, for example, the “Diamond-problem” scenario.
-
Imagine an
ElectronicDevice
class that has some behaviors that all electronic devices have, such as powering on and off. -
Next, imagine a
Scanner
class - a document scanner is an electronic device, so it makes sense to inherit the code fromElectronicDevice
class. -
Next, imagine a
Printer
class - a document printer is also an electronic device, so it makes sense also to inherit the code fromElectronicDevice
class.
Example scenario (continued)
The following UML diagram shows our classes so far, with some example properties and methods.
Example scenario (continued again)
So far so good. But what if we added a Copier
to the mix?
- A
Copier
has behaviors of both aScanner
and aPrinter
. Ideally, it would inherit the public interfaces of both.
The problem
In Java, and many other languages, inheritance from multiple classes is impossible.
- The only working solution would be to use composition rather than inheritance.
public class Copier {
private Scanner scanner;
private Printer printer;
public Copier(Scanner scanner, Printer printer) {
this.scanner = scanner;
this.printer = printer;
}
}
-
Why this limitation?
-
Because what if
Scanner
andPrinter
had a method by the same name, let’s saystart()
. Which version wouldCopier
inherit?
Conclusions
You have now extended your understanding of object-oriented programming to include class-based inheritance and polymorphism.
- Thank you. Bye.