This content originally appeared on DEV Community and was authored by Maxi Contieri
We learned at school that inheritance represents an 'is-a' relationship. It is not.
TL:DR; Think about protocol and behavior, forget inheritance
Problems
Bad models
Unexpected behavior
Subclass overrides
Liskov substitution principle Violation
Solutions
Think in terms of behavior behaves-as-a
Prefer composition over inheritance
Subclassify always following 'behaves-as-a' relation
Context
IS-A relation comes from the data world.
We learned ERDs with structured design and data modeling.
Now, we need to think in terms of behavior.
Behavior is essential, data is accidental.
Sample Code
Wrong
// If you made Square derive from Rectangle, then a Square should be usable anywhere you expect a rectangle
#include <iostream>
Rectangle::Rectangle(const unsigned width, const unsigned height):
m_width(width),
m_height(height)
{
}
unsigned Rectangle::getWidth() const
{
return m_width;
}
void Rectangle::setWidth(const unsigned width)
{
/*Width and Height are independant*/
m_width = width;
}
unsigned Rectangle::getHeight() const
{
return m_height;
}
void Rectangle::setHeight(const unsigned height)
{
m_height = height;
}
unsigned Rectangle::area() const
{
/*Valid for both Rectangles and Squares*/
return m_height * m_width;
}
Square::Square(const unsigned size)
: Rectangle(size, size)
{
}
// OK for squares, bad for rectangles
// Protocol is bad, width and height are not relevant on squares
void Square::setWidth(const unsigned size)
{
m_height = size;
m_width = size;
}
// OK for squares, bad for rectangles
// Protocol is bad, width and height are not relevant on squares
void Square::setHeight(const unsigned size)
{
m_height = size;
m_width = size;
}
void process(Rectangle& r)
{
unsigned h = 10;
auto w = r.getWidth();
r.setHeight(h);
std::cout << "Expected area: " << (w*h) << ", got " << r.area() << "\n";
//area is not well defined in squares
//every square IS-A rectangle, but does not behave-like a rectangle
}
int main()
{
Rectangle rectangle{3,4};
Square square{5};
process(rectangle);
process(square);
}
Right
// If you made Square derive from Rectangle, then a Square should be usable anywhere you expect a rectangle
#include <iostream>
Rectangle::Rectangle(const unsigned width, const unsigned height):
m_width(width),
m_height(height)
{
}
unsigned Rectangle::getWidth() const
{
return m_width;
}
void Rectangle::setWidth(const unsigned width)
{
/*Width and Height are independant*/
m_width = width;
}
unsigned Rectangle::getHeight() const
{
return m_height;
}
void Rectangle::setHeight(const unsigned height)
{
m_height = height;
}
unsigned Rectangle::area() const
{
return m_height * m_width;
}
//No inheritance
Square::Square(const unsigned size):
m_size(size)
{
}
unsigned Square::area() const
{
return m_size * m_size;
}
void Square::setSize(const unsigned size)
{
m_size = size;
}
void process(Rectangle& r)
{
unsigned h = 10;
auto w = r.getWidth();
r.setHeight(h);
std::cout << "Expected area: " << (w*h) << ", got " << r.area() << "\n";
//area is not well defined in squares
//every square IS-A rectangle, but does not behave-like a rectangle
}
int main()
{
Rectangle rectangle{3,4};
Square square{5};
process(rectangle);
process(square);
}
Detection
[X] Manual
This is a semantic smell.
Tags
- Inheritance
Conclusion
Real Number IS-A Complex number (according to math).
Integer IS-A Real number (according to math).
Real Number does not Behave-Like-A Complex number.
We cannot do real.setImaginaryPart() so it is not a Complex according to our Bijection
Relations
Code Smell 92 - Isolated Subclasses Names
Maxi Contieri ・ Oct 11 '21 ・ 2 min read
Code Smell 11 - Subclassification for Code Reuse
Maxi Contieri ・ Oct 30 '20 ・ 2 min read
Code Smell 37 - Protected Attributes
Maxi Contieri ・ Nov 29 '20 ・ 2 min read
More Info
Credits
Photo by Joshua Rondeau on Unsplash
DRY - Don't Repeat Yourself - Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Andy Hunt
Software Engineering Great Quotes
Maxi Contieri ・ Dec 28 '20 ・ 13 min read
This article is part of the CodeSmell Series.
How to Find the Stinky parts of your Code
Maxi Contieri ・ May 21 '21 ・ 4 min read
This content originally appeared on DEV Community and was authored by Maxi Contieri
Maxi Contieri | Sciencx (2022-03-26T14:58:18+00:00) Code Smell 125 – ‘IS-A’ Relationship. Retrieved from https://www.scien.cx/2022/03/26/code-smell-125-is-a-relationship/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.