← Back to Tools

SOLID Principles

A beginner-friendly guide for Unity developers using C#

Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
S
Single Responsibility Principle
A class should only do one thing

Each class should have one job. If you need to change how an entity moves and how health works, those should be separate scripts — not crammed into a single MonoBehaviour.

✗ Doing too much
public class Entity : MonoBehaviour { // Movement, health, inventory, // audio, UI... all in one class! void Move() { ... } void TakeDamage() { ... } void UpdateUI() { ... } void PlaySound() { ... } }
✓ One job per class
public class EntityMovement : MonoBehaviour { void Move() { ... } } public class EntityHealth : MonoBehaviour { void TakeDamage() { ... } }
O
Open-Closed Principle
Extend it, don't change it

Once a class is written and tested, you shouldn't need to edit it to add new behavior. Instead, design it so new features can be added by creating new classes. This way, you never risk breaking something that already works.

✗ Editing old code for each new type
public class Weapon { public void Attack(string type) { // Every new weapon = edit this if (type == "Sword") { ... } else if (type == "Bow") { ... } else if (type == "Staff") { ... } } }
✓ New weapons without touching old code
public abstract class Weapon : MonoBehaviour { public abstract void Attack(); } public class Sword : Weapon { public override void Attack() { // Sword-specific logic } }
L
Liskov's Substitution Principle
Children must work wherever parents do

If two classes share the same base type, any code using that base type should work correctly with either child — no surprises, no crashes, no special cases.

✓ Example — Any enemy works in the same spawner
public abstract class Enemy : MonoBehaviour { public abstract void Spawn(Vector3 position); } public class Goblin : Enemy { public override void Spawn(Vector3 position) { transform.position = position; // Goblin setup... } } public class Dragon : Enemy { public override void Spawn(Vector3 position) { transform.position = position; // Dragon setup... } } // This spawner works with ANY enemy — Goblin, Dragon, or future ones public class EnemySpawner : MonoBehaviour { public void SpawnEnemy(Enemy enemy, Vector3 pos) { enemy.Spawn(pos); // Works the same regardless of type } }
I
Interface Segregation Principle
Keep interfaces small and focused

Don't force a class to implement things it doesn't need. Use small, specific interfaces — each with the least amount of members — so classes only commit to what they actually use.

✗ One bloated interface
public interface IEntity { void TakeDamage(int amount); void Move(Vector3 dir); void OpenInventory(); } // A tree can take damage... // but Move? OpenInventory? Nope. public class Tree : IEntity { ... }
✓ Small, focused interfaces
public interface IDamageable { void TakeDamage(int amount); } public interface IMovable { void Move(Vector3 dir); } // Tree only implements what it needs public class Tree : IDamageable { ... } // Player uses both public class Player : IDamageable, IMovable { ... }
D
Dependency Inversion Principle
Depend on interfaces, not concrete classes

High-level code shouldn't depend on specific implementations. Use interfaces and abstract classes so you can swap behaviors (like different save systems or input methods) without rewriting the code that uses them.

✗ Locked to one implementation
public class GameManager : MonoBehaviour { // Directly depends on a specific class private JsonSaveSystem _saveSystem; void Save() { _saveSystem.SaveGame(); } }
✓ Depends on an interface
public interface ISaveSystem { void SaveGame(); } public class JsonSave : ISaveSystem { ... } public class CloudSave : ISaveSystem { ... } public class GameManager : MonoBehaviour { // Works with ANY save system private ISaveSystem _saveSystem; void Save() { _saveSystem.SaveGame(); } }
You don't need all five at once. Start with Single Responsibility — split your giant EntityController into smaller scripts. Once that feels natural, try the others one at a time. SOLID is a guide, not a law. In game jams or prototypes, it's fine to bend the rules and refactor later.