'Tis the season to be jolly, and what better way to get into the holiday spirit than with a technical blog post about object-oriented programming (OOP)? In this festive post, I'll explore the concept of inheritance and why it's important to ignore reality to use it appropriately.
Even if Santa Claus was real (which he totally is – I checked twice!), he wouldn't be able to inherit from the class Human.
What is Inheritance?
In OOP, inheritance is a way for one class to inherit the characteristics and behavior of another class. It allows us to create a hierarchy of classes and represent an "is-a" relationship. For example, a "Dog" class could inherit from a "Mammal" class, because a dog is a type of mammal.
Why Santa Claus Can't Inherit from Human
While it might seem logical at first to make Santa Claus inherit from Human, there are several reasons why this is not a good idea.
Inheritance is typically used to represent an "is-a" relationship, meaning that the subclass (in this case, Santa Claus) is a more specific type of the superclass (Human). However, Santa Claus is not a type of human – he has unique characteristics and abilities that do not apply to all humans. For example, Santa Claus has a list of naughty and nice children, a magical sleigh that can fly around the world in a single night, and the ability to deliver presents to every good boy and girl on Earth.
You might think, sure, that’s how it works, you have a more generic base with more specific things on top. But reality and programming work differently. For example, if we try to make Santa Claus inherit from Human, we might end up with methods such as "getAge()" or "getHeight()" that do not make sense for a character who is hundreds of years old and can shrink or grow to fit through chimneys.
A Better Approach
A better approach would be to create a separate class for Santa Claus and define his characteristics and behavior in that class. This way, we can represent him accurately and avoid any confusion or unnecessary checks all over the code. After all, we wouldn't want any naughty code sneaking its way into our nice list!
Square and Rectangle
Another example of inappropriate inheritance is trying to make a class "Square" inherit from a class "Rectangle". While a square is a type of rectangle in real life, it has its own unique characteristics (such as all sides being equal in length) that are not shared by all rectangles. In this case, for programming purposes, it would be better to create a separate class for Square and define its characteristics and behavior independently.
Here's an example of what can happen when we make Square inherit from Rectangle:
class Rectangle {
constructor(public length: number, public width: number) {}
getArea(): number {
return this.length * this.width;
}
setLength(length: number): void {
this.length = length;
}
setWidth(width: number): void {
this.width = width;
}
}
class Square extends Rectangle {
constructor(sideLength: number) {
super(sideLength, sideLength);
}
}
const square = new Square(5);
console.log(square.getArea()); // 25
square.setLength(10);
console.log(square.getArea()); // 50
In this code, we have a Rectangle
class with a getArea()
method for calculating the area of the rectangle, as well as setter methods for modifying the length and width of the rectangle. We also have a Square
class that inherits from Rectangle
, but overrides the constructor to set the length and width to be equal (since they are equal in a square).
At first, when we create a Square
object and call the getArea()
method, it works as expected and returns the correct value. However, when we use the setLength()
setter method to change the length of the square, it breaks the expected behavior of a square.
In a square, all sides are equal, so changing the length of the square should also change the width. However, because we are inheriting from a Rectangle
class and not accurately representing the characteristics of a square, this does not happen. As a result, we end up with an incorrect value for the area.
A better approach would be to create a separate class for Square and define its own setter methods that ensure that all sides remain equal when one of them is changed. This way, we can avoid any bugs or unrealistic behavior in our code.
class Square {
constructor(public sideLength: number) {}
getArea(): number {
return Math.pow(this.sideLength, 2);
}
setSideLength(sideLength: number): void {
this.sideLength = sideLength;
}
}
const square = new Square(5);
console.log(square.getArea()); // 25
square.setSideLength(10);
console.log(square.getArea()); // 100
In this revised code, we have a separate Square
class with its own setter method for changing the side length of the square. When we use this setter method to change the side length, it correctly updates all sides of the square and maintains the expected behavior.
Additionally, if we tried to support a square as a subclass of the rectangle, we would need to include a lot of conditional statements in the code to account for the square or override them in the class square and even then we would still end up with methods like setLength()
and setHeight()
.
This would result in a lot of extra code and complexity, making it harder to understand and maintain the codebase. Instead, it is much simpler and more effective to create a separate class for Square. This way, we can clearly represent the unique characteristics of a square and avoid any unnecessary complexity in the code.
Finally
In some cases, such as with Santa Claus and Square, it’s better to ignore conceptions from real life in favor of doing what will work better for programming concepts.
So, take this time and do like Santa, checking your list of classes and separating them into nice and naughty classes, and then checking twice.
Happy holidays, and happy coding!
Cover Photo by Mike Arney on Unsplash