Code Smell 125 — ‘IS-A’ Relationship

We learned at school that inheritance represents an ‘is-a’ relationship. It is not.

Problems

  • Bad models
  • Unexpected behavior
  • Subclass overrides
  • Liskov substitution principle Violation

Solutions

  1. Think in terms of behavior behaves-as-a
  2. Prefer composition over inheritance
  3. Subclassify always following ‘behaves-as-a’ relation

Context

IS-A relation comes from the data world.

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

Tags

  • Inheritance

Conclusion

Real Number IS-A Complex number (according to math) Integer IS-A Real number (according to math)

Relations

More Info

Credits

Photo by Joshua Rondeau on Unsplash

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Maximiliano Contieri

I’m a senior software engineer specialized in declarative designs. S.O.L.I.D. and agile methodologies fan. Maximilianocontieri.com