# Code Smell 125 — ‘IS-A’ Relationship

## 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

# 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*

This article is part of the CodeSmell Series.