SOLID 원칙 [TIL 35일차]

장민제·2025년 6월 2일

내일배움캠프

목록 보기
37/41
post-thumbnail

오늘은 객체지향 설계의 핵심 원칙인 SOLID에 대해 공부했고, 이를 게임 개발 관점에서 어떻게 적용할 수 있는지도 함께 정리해봤다.
이 원칙들은 Unity 같은 엔진에서 개발할 때도 유지보수성확장성을 높이기 위해 매우 중요하다.


📝 SOLID?

  • 객체지향 설계의 5가지 핵심원칙
원칙이름핵심 개념
SSingle Responsibility Principle하나의 클래스는 하나의 책임만 가져야 한다
OOpen/Closed Principle기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다
LLiskov Substitution Principle자식 클래스는 부모 클래스를 대체할 수 있어야 한다
IInterface Segregation Principle불필요한 인터페이스 구현을 강요하지 않아야 한다
DDependency Inversion Principle구체 클래스가 아닌 추상화에 의존해야 한다

🎮 게임 개발에서의 적용 예시

1️⃣ SRP - 단일 책임 원칙

하나의 스크립트/컴포넌트는 하나의 역할만 담당해야 한다.

// ❌ 이동, 공격, 저장 로직이 한 클래스에 몰려 있음
public class Player : MonoBehaviour {
    void Move() { }
    void Attack() { }
    void SaveData() { }
}

// ✅ 역할을 명확히 분리
public class PlayerMovement : MonoBehaviour { void Move() { } }
public class PlayerCombat : MonoBehaviour { void Attack() { } }
public class PlayerSave : MonoBehaviour { void SaveData() { } }
  • 장점: 기능별 책임 분리로 유지보수가 쉬움, 재사용도 가능

2️⃣ OCP - 개방-폐쇄 원칙

코드를 수정하지 않고, 기능을 확장할 수 있어야 한다

// 공격방식을 인터페이스로 추상화
public interface IAttackStrategy 
{
    void Attack();
}

public class MeleeAttack : IAttackStrategy { public void Attack() { } }
public class RangedAttack : IAttackStrategy { public void Attack() { } }

public class Enemy {
    private IAttackStrategy attackStrategy;
    
    public Enemy(IAttackStrategy strategy) 
    {
        this.attackStrategy = strategy;
    }
    public void Attack() => attackStrategy.Attack();
}
  • 장점: 새로운 공격 방식이 생겨도 기존 코드를 건드릴 필요 없음

3️⃣ LSP - 리스코프 치환 원칙

부모 클래스를 자식 클래스로 치환해도 동작이 깨지면 안 된다.

// X 자식 클래스가 부모의 계약을 깨뜨림
public class Enemy { public virtual void Die() { } }
public class ImmortalEnemy : Enemy 
{
    public override void Die() => throw new Exception("죽지 않음");
}

// O 공통 기능만 인터페이스로 분리
public interface IDamageable 
{
    void TakeDamage(int amount);
}

public class RegularEnemy : MonoBehaviour, IDamageable { }
public class ImmortalEnemy : MonoBehaviour { }
  • 포인트: 상속보다는 인터페이스 분리와 조합을 더 많이 사용하자

4️⃣ ISP - 인터페이스 분리 원칙

필요한 기능만 명확하게 나눈 인터페이스를 사용하자.

// X 모든 엔티티에 다 필요한 메서드는 아님
public interface IGameEntity {
    void Move();
    void Attack();
    void CastSpell();
}

// O 기능별로 인터페이스를 분리
public interface IMovable { void Move(); }
public interface IAttackable { void Attack(); }
public interface ISpellCaster { void CastSpell(); }
  • 장점: 필요 없는 기능 구현을 강제하지 않아 클래스가 깔끔해짐

5️⃣ DIP - 의존 역전 원칙

클래스는 구체적인 구현보다 인터페이스(추상화)에 의존해야 한다.

// X GameManager가 AudioManager에 직접 의존
public class GameManager 
{
    private AudioManager audioManager = new AudioManager();
}

// O 추상화에 의존하여 유연성 확보
public interface IAudioManager 
{
    void Play(string clip);
}

public class GameManager 
{
    private IAudioManager audio;
    
    public GameManager(IAudioManager audio) 
    {
        this.audio = audio;
    }
}
  • 포인트: DI(의존성 주입)를 통해 테스트와 유연성 확보

📃 정리

게임도 결국 소프트웨어다. 설계가 깔끔해야 확장도 쉽다.

  • SRP → 기능별 컴포넌트 분리로 유지보수 편리
  • OCP → 새로운 무기/스킬/몬스터도 쉽게 추가 가능
  • LSP → 상속보다 인터페이스 조합이 유리한 경우가 많음
  • ISP → 엔티티마다 필요한 기능만 제공
  • DIP → 테스트 가능한 구조, 교체 가능한 모듈 구성 가능

항상 지키며 작업할 수는 없겠지만 최대한 지켜려고 노력하고,
앞으로는 interface를 더 활용하여 설계를 해봐야겠다.

profile
Unity, C#

0개의 댓글