Interface-Driven Development
Overview
Eliminate rigidity of deep inheritance and hard-coupling by programming to interfaces (contracts). Creates modular, testable systems where implementations can be swapped without modifying consumers.
When to Use
- •Use when different objects share behaviors (enemies and walls both damageable)
- •Use when implementing dependency injection patterns
- •Use when creating mock objects for unit testing
- •Use when building plugin/extension architectures
- •Use when you see "if (obj is TypeA) else if (obj is TypeB)" patterns
Core Principle
code
┌─────────────────────────────────────────────────────────────┐ │ PROGRAM TO INTERFACES │ │ (Not to implementations) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ BAD: Player attacks Enemy (tight coupling) │ │ │ │ GOOD: Player attacks IDamageable (any implementing type) │ │ │ └─────────────────────────────────────────────────────────────┘
Common Unity Interfaces
| Interface | Purpose | Implementations |
|---|---|---|
IDamageable | Can receive damage | Enemy, Player, Destructible |
IInteractable | Can be interacted with | Door, Chest, NPC, Lever |
ISaveable | Persists state | Player, Inventory, QuestLog |
IPoolable | Object pool lifecycle | Projectile, VFX, Enemy |
IInitializable | Async initialization | Managers, Services |
Interface Design Guidelines
csharp
// GOOD: Small, focused interface (ISP)
public interface IDamageable
{
void TakeDamage(float amount);
bool IsAlive { get; }
}
// GOOD: Separate concern
public interface IHealable
{
void Heal(float amount);
float MaxHealth { get; }
}
// BAD: Fat interface (violates ISP)
public interface IEntity
{
void TakeDamage(float amount);
void Heal(float amount);
void Move(Vector3 direction);
void Attack();
void Die();
// Too many responsibilities!
}
Best Practices
- •✅ Interfaces start with
Iprefix (e.g.,IDamageable) - •✅ Keep interfaces small and focused (Interface Segregation)
- •✅ Place interfaces in dedicated files to prevent circular deps
- •✅ Use
TryGetComponent<IInterface>for safe access - •✅ Design interfaces without MonoBehaviour dependencies when possible
- •❌ NEVER use "Base" suffix if interface works (use
IEnemynotEnemyBase) - •❌ NEVER cast interface back to concrete type
- •❌ NEVER include implementation details in interface
Procedure
- •Extract Behaviors: Identify shared behaviors across types
- •Define Contract: Create minimal interface with essential methods
- •Implement: Have classes implement the interface
- •Reference by Interface: Consumers use interface type, not concrete
- •Test: Create mock implementations for unit testing
Few-Shot Examples
Example 1: Unified Damage System
User: "Same attack should damage enemies AND breakable walls."
Agent:
csharp
public interface IDamageable
{
void TakeDamage(float amount);
bool IsAlive { get; }
}
// Enemy.cs
public class Enemy : MonoBehaviour, IDamageable { ... }
// DestructibleWall.cs
public class DestructibleWall : MonoBehaviour, IDamageable { ... }
// WeaponAttack.cs - works on ANY IDamageable
void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<IDamageable>(out var target))
{
target.TakeDamage(_damage);
}
}
Example 2: Interaction System
User: "Player can interact with doors, chests, and NPCs."
Agent:
csharp
public interface IInteractable
{
string InteractionPrompt { get; }
bool CanInteract { get; }
void Interact(GameObject interactor);
}
// Door : IInteractable -> Opens
// Chest : IInteractable -> Shows loot
// NPC : IInteractable -> Starts dialogue
Example 3: Save System
User: "Multiple objects need to save state."
Agent:
csharp
public interface ISaveable
{
string SaveId { get; }
object GetSaveData();
void LoadSaveData(object data);
}
// SaveManager finds all ISaveable, calls GetSaveData()
Mockability for Testing
csharp
// Production
public class AudioManager : IAudioService { ... }
// Test Mock
public class MockAudioService : IAudioService
{
public bool WasSoundPlayed { get; private set; }
public void PlaySound(string id) => WasSoundPlayed = true;
}
Related Skills
- •
@di-container-manager- Inject interface implementations - •
@advanced-design-patterns- Strategy, Factory use interfaces - •
@advanced-game-bootstrapper- IInitializable pattern
Safe Access Patterns
csharp
// GOOD: Safe component access
if (TryGetComponent<IDamageable>(out var damageable))
{
damageable.TakeDamage(10f);
}
// BAD: Assumes component exists
GetComponent<IDamageable>().TakeDamage(10f); // NullRef risk!