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 classEntity : MonoBehaviour
{
// Movement, health, inventory,// audio, UI... all in one class!voidMove() { ... }
voidTakeDamage() { ... }
voidUpdateUI() { ... }
voidPlaySound() { ... }
}
✓ One job per class
public classEntityMovement : MonoBehaviour
{
voidMove() { ... }
}
public classEntityHealth : MonoBehaviour
{
voidTakeDamage() { ... }
}
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 classWeapon
{
public voidAttack(stringtype)
{
// Every new weapon = edit thisif (type == "Sword") { ... }
else if (type == "Bow") { ... }
else if (type == "Staff") { ... }
}
}
✓ New weapons without touching old code
public abstract classWeapon : MonoBehaviour
{
public abstract voidAttack();
}
public classSword : Weapon
{
public override voidAttack()
{
// 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 classEnemy : MonoBehaviour
{
public abstract voidSpawn(Vector3position);
}
public classGoblin : Enemy
{
public override voidSpawn(Vector3position)
{
transform.position = position;
// Goblin setup...
}
}
public classDragon : Enemy
{
public override voidSpawn(Vector3position)
{
transform.position = position;
// Dragon setup...
}
}
// This spawner works with ANY enemy — Goblin, Dragon, or future onespublic classEnemySpawner : MonoBehaviour
{
public voidSpawnEnemy(Enemyenemy, Vector3pos)
{
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 interfaceIEntity
{
voidTakeDamage(intamount);
voidMove(Vector3dir);
voidOpenInventory();
}
// A tree can take damage...// but Move? OpenInventory? Nope.public classTree : IEntity { ... }
✓ Small, focused interfaces
public interfaceIDamageable
{
voidTakeDamage(intamount);
}
public interfaceIMovable
{
voidMove(Vector3dir);
}
// Tree only implements what it needspublic classTree : IDamageable { ... }
// Player uses bothpublic classPlayer : 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 classGameManager : MonoBehaviour
{
// Directly depends on a specific classprivateJsonSaveSystem_saveSystem;
voidSave()
{
_saveSystem.SaveGame();
}
}
✓ Depends on an interface
public interfaceISaveSystem
{
voidSaveGame();
}
public classJsonSave : ISaveSystem { ... }
public classCloudSave : ISaveSystem { ... }
public classGameManager : MonoBehaviour
{
// Works with ANY save systemprivateISaveSystem_saveSystem;
voidSave()
{
_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.