Note: This article demonstrates problematic inheritance practices that violate SOLID principles. This is presented as an educational example of what to avoid in your own code design.
Context: Why This Exists (And Why You Shouldn't Copy It)
Unity's MonoBehaviour inheritance hierarchy represents a real-world example of deep inheritance that, while necessary for the engine's architecture, demonstrates several design principles that should generally be avoided in application code. This inheritance hierarchy exists in Unity's engine code for specific architectural reasons:
Performance: Direct access to native C++ engine code through inheritance
Legacy: Years of API evolution and backward compatibility requirements
Framework Requirements: Unity needs a unified lifecycle management system
Important: This is library/framework code that you consume, not application code that you should design this way.
The Hierarchy and Its Issues
The diagram at the end of this article shows the inheritance chain from Object → Component → Behaviour → MonoBehaviour. Each level adds significant functionality and complexity:
This deep inheritance hierarchy makes extending behavior difficult:
New Unity features often require changes throughout the hierarchy
Developers must inherit the entire implementation stack, even for simple functionality
Liskov Substitution Principle (LSP)
When inheriting from MonoBehaviour:
You inherit massive amounts of implementation
It's easy to violate LSP by overriding methods in ways that break expected behavior
The "Awake before Start before Update" lifecycle constraints create fragile dependencies
Interface Segregation Principle (ISP)
The most obvious violation in this hierarchy:
MonoBehaviour exposes dozens of methods and properties
Most scripts use only a tiny fraction of the inherited API surface
Every script is forced to inherit this massive interface
Dependency Inversion Principle (DIP)
The hierarchy creates tight coupling:
Scripts depend directly on concrete implementation details
Cannot easily substitute alternative implementations
Testing becomes difficult due to engine dependencies
Why Didn't Unity Choose Composition?
Unity's designers had several constraints that led to this inheritance-heavy approach:
Historical Reasons
Unity began development in the early 2000s when inheritance was more favored
C++ background of engine developers influenced the design
Unity adopted a single, intuitive base class (MonoBehaviour) without requiring explicit overrides or using composition, matching how beginners naturally structure code. (TBD) (This point is the one I spent the most time on. And I don't have any evidence to back it up, but it just feels right.)
Technical Constraints
Need for direct native code interop
Serialization system requirements
Editor integration demands
This would provide better separation of concerns but would require a completely different engine architecture.
Lessons for Your Own Code
What NOT to Do
Don't create deep inheritance hierarchies like this in your application code
Don't put multiple responsibilities in base classes
Don't inherit just for code reuse (favor composition instead)
Don't force clients to depend on functionality they don't need
What TO Do Instead
Favor composition over inheritance for your game scripts
Use interfaces to define contracts (like ISwitchable in our other examples)
Keep inheritance shallow—prefer 2–3 levels maximum
Use inheritance for "is-a" relationships only
Working WITH Unity's Inheritance
Since you must inherit from MonoBehaviour, follow these guidelines:
// Good: Focused responsibility
public class HealthDisplay : MonoBehaviour
{
[SerializeField] private HealthComponent healthComponent;
[SerializeField] private Text healthText;
void Update()
{
healthText.text = $"Health: {healthComponent.CurrentHealth}";
}
}
// Bad: Multiple responsibilities inherited and extended
public class Player : MonoBehaviour
{
// Movement
public float moveSpeed = 5f;
// Health
public int health = 100;
// Inventory
public List<Item> inventory = new List<Item>();
// Combat
public void Attack() { /* ... */ }
// Input handling
void Update() { /* Handle all player input */ }
// Don't put all game logic directly in MonoBehaviour
// Instead, compose with other classes
}
Deep Dive: Specific Problems
Massive Interface Surface
The MonoBehaviour class exposes dozens of methods and properties. Every class that inherits from it gets:
50+ methods from Object (Instantiate, Destroy, FindObjectsOfType, etc.)
20+ methods from Component (GetComponent, SendMessage, etc.)
15+ methods from MonoBehaviour (StartCoroutine, Invoke, etc.)
This violates the Interface Segregation Principle—most scripts don't need 90% of these methods.
Hidden Dependencies
When you inherit from MonoBehaviour, you implicitly depend on:
Unity's serialization system
The GameObject/Component architecture
Unity's execution order system
The native engine runtime
Editor integration systems
This makes testing difficult and creates tight coupling.
Violation of Liskov Substitution Principle
It's very easy to create MonoBehaviour subclasses that aren't truly substitutable:
// This violates LSP - requires specific setup
public class DatabaseConnection : MonoBehaviour
{
void Start()
{
// Assumes specific GameObject setup
// Breaks if used differently
}
}
Takeaways
Library code has different constraints than application code
Unity's inheritance hierarchy serves specific architectural needs but shouldn't be emulated
When forced to inherit (like from MonoBehaviour), keep your classes focused and use composition internally
Learn from both good and bad examples - this is a case study in what not to do in your own designs
Understanding why this code exists helps you make better decisions in your own object-oriented designs. See Also: