디자인패턴

IIRU·2026년 8월 5일

디자인 패턴 ?

소프트웨어를 개발하면서 반복적으로 발생하는 구조적 문제를 해결하기 위해 정리된 재사용 가능한 설계 방법

싱글톤

특정 클래스의 인스턴스가 하나만 존재하도록 하고, 어디서든 접근할 수 있게 만드는 패턴

using UnityEngine;

public class GameManager : MonoBehavior
{
		public static GameManager instance;
		
		private void Awake()
		{
				if(instance == null)
				{
						instance = this;		
				}
				else
				{
						Destroy(gameObject);
				}
		}

}

public void GameStart()
{
		Debug.Log("게임시작");
}

사용방법

GameManager.instance.GameStart();

장점

어디서든 쉽게 접근 가능하다
객체를 하나만 유지 가능하다.
공통 시스템 관리에 편리하다.

단점

전역변수처럼 많이 쓰일 수 있다.
클래스 간 결합도가 높아질 수 있다. < 주의
초기화 순서 문제가 발생할 수 있다.

State

객체의 상태를 별도의 클래스로 분리하고, 현재의 상태에 따라 행동이 달라지도록 만드는 패턴

인터페이스를 만들어서

public interface IState
{
    void Enter();
    void Update();
    void Exit();
}

상태 변경하는 함수로 제어할 수 있도록 한다.

public void ChangeState(IState nextState)
{
    currentState?.Exit();

    currentState = nextState;

    currentState?.Enter();
}

상태 클래스 사용

public class Attack : Istate
{
		void Enter()
		{
			// 상태 진입 시 한 번 실행
		}
    void Update()
    {
		  // 상태 유지 중 반복 실행
    }
    void Exit()
    {
		  // 상태 종료 시 한 번 실행  
    }
}

상태 패턴과 FSM은 서로 대조되는 개념이 아니라 '이론'과 '구현 방식'의 관계이다.

Strategy Pattern

여러 행동이나 알고리즘을 각각 분리하고, 필요한 방식을 선택하거나 교체해서 사용하는 패턴

public interface IAttackStrategy
{
    void Attack();
}
public class SwordAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("검 공격");
    }
}
public class BowAttack : IAttackStrategy
{
    public void Attack()
    {
        Debug.Log("활 공격");
    }
}

사용방법

public class PlayerAttack
{
		private IAttackStrategy attStrategy;
		
		public void SetStrategy(IAttackStrategy strategy)
		{
				attackStrategy = strategy;
		}
		
		public void Attack()
		{
				attackStrategy?.Attack();
		}
}
playerAttack.SetStrategy(new SwordAttack());

장점

조건문을 줄일 수 있다.
실행 중 행동 교체가 가능하다.
새로운 행동을 추가하기 쉽다.

단점

전략마다 클래스가 필요하다. < 클래스가 많아짐.
간단한 기능에 적용하면 복잡해질 수 있다.

Observer Pattern

한 객체의 상태가 변경될 때, 구독한 여러 객체에 변경 사실을 알리는 패턴.

PlayerHealth에 피격 이벤트가 발생했다 → UI / Sound / Controller에서 실행하거나 변경.

using System;

public class PlayerHealth : MonoBehaviour
{
    public event Action<int, int> OnHealthChanged;
    public event Action OnDead;

    public void TakeDamage(int damage)
    {
        currentHealth -= damage;

        OnHealthChanged?.Invoke(currentHealth, maxHealth);

        if (currentHealth <= 0)
        {
            OnDead?.Invoke();
        }
    }
}

이미 다른 클래스에서 구독된 이벤트를 사용만 하도록.

private void OnEnable()
{
    playerHealth.OnHealthChanged += UpdateHealthUI;
}

private void OnDisable()
{
    playerHealth.OnHealthChanged -= UpdateHealthUI;
}

장점

객체 간의 직접 참조를 줄일 수 있다.
하나의 이벤트에 여러 객체가 반응 가능하다.
게임 로직과 UI를 분리하기가 좋다.

단점

이벤트 흐름을 추적하기 어렵다. < 여러 클래스에 나눠진 이벤트를 찾아야함
구독 해제를 하지 않으면 중복으로 호출될 수 있다. < 구독이 계속 중첩될 수 있다.
이벤트가 많아지면 디버깅이 어려워진다.

Prototype Pattern

새로운 객체를 처음부터 만드는 대신, 기존의 객체를 복제하여 새로운 객체를 만드는 패턴.

프리펩을 사용하여 객체에 복사하는 방식 / ScriptableObject로 만든 데이터를 복사해서 가져오는 방식

장점

복잡한 객체를 쉽게 생성가능
초기 설정을 반복하지 않아도됌.
원본을 기준으로 변형하는 것이 쉬움.

단점

참조형 데이터가 공유되어 원본 훼손 가능.

Template Method Pattern

알고리즘 전체 실행 순서를 부모 클래스에서 정의하고, 일부 단계만 자식 클래스에서 변경하는 패턴.

public abstract class Weapon
{
    public void Attack()
    {
        PrepareAttack();
        ExecuteAttack();
        FinishAttack();
    }

    protected virtual void PrepareAttack()
    {
        Debug.Log("공격 준비");
    }

    protected abstract void ExecuteAttack();

    protected virtual void FinishAttack()
    {
        Debug.Log("공격 종료");
    }
}

클래스를 상속받아 자식 클래스는 실제 공격 부분만 구현한다.

public class Sword : Weapon
{
    protected override void ExecuteAttack()
    {
        Debug.Log("검 공격");
    }
}

전체 순서는 부모 클래스가 정함. 나머지 일부 과정만 자식 클래스가 구현한다.

장점

공통 코드 중복을 줄일 수 있다.
실행 순서를 일정하게 유지할 수 있다.
자식 클래스는 필요한 부분만 구현할 수 있다.

단점

상속에 강하게 의존한다.
부모 클래스 변경이 자식에게 영향을 준다.
실행 중에 행동을 바꾸기 힘들다. < 위에 예시를 보면 실제 공격부분만 구현되기때문에 준비 종료 구간을 다르게 구현하기 힘들다.

Facade Pattern

여러 개의 복잡한 시스템을 하나의 단순한 인터페이스로 묶어 제공하는 패턴.

예를 들면 게임시작을 할때 여러가지 선행되서 실행되는 UI라던가 BGM같이 필수적으로 실행되는 공통 작업을 묶어둔것.

public class GameFacade : MonoBehaviour
{
    [SerializeField] private UIManager uiManager;
    [SerializeField] private SoundManager soundManager;
    [SerializeField] private PlayerSpawner playerSpawner;
    [SerializeField] private RoomManager roomManager;

    public void StartGame()
    {
        uiManager.ShowGameUI();
        soundManager.PlayBattleBGM();
        roomManager.StartRoom();
        playerSpawner.SpawnPlayer();
        scoreManager.SetScore(0);
    }

    public void EndGame()
    {
        roomManager.StopRoom();
        soundManager.PlayGameOverBGM();
        uiManager.ShowGameOverUI();
    }
}

장점

복잡한 기능을 묶어두어서 간단하게 사용가능하다.
호출 순서를 통일할 수 있다.
외부 객체의 의존성을 줄일 수 있다.

단점

Facade가 너무 커질 수 있다.
모든 기능이 하나의 클래스에 집중될 수 있다. ← god class 조심

MVC Pattern

프로그램을 Model, View, Controller로 분리하여 데이터, 화면, 제어 역할을 나누는 구조패턴.

입력 → Controller → Model변경 → View갱신 순서로 이루어짐

public class PlayerController
{
    private PlayerModel model;
    private PlayerView view;

    public PlayerController(PlayerModel model, PlayerView view)
    {
        this.model = model;
        this.view = view;
    }

    public void TakeDamage(int damage)
    {
        model.TakeDamage(damage);
        view.UpdateHealth(model.CurrentHealth, model.MaxHealth);
    }
}

Controller에서 Model과 View를 제어

장점

데이터와 화면을 분리 가능.
역할이 명확하다.
UI가 변경되도 Model을 재사용하기 쉽다.

단점

Controller에 코드가 집중될 수 있다.
Unity구조와 완전히 일치하지 않을 수 있다.

MVP Pattern

Model, View, Presenter로 역할을 나누고, Presenter가 Model과 View 사이의 통신을 담당하는 구조패턴.

public class PlayerPresenter
{
    private PlayerModel model;
    private IPlayerView view;

    public PlayerPresenter(PlayerModel model, IPlayerView view)
    {
        this.model = model;
        this.view = view;
    }

    public void TakeDamage(int damage)
    {
        model.TakeDamage(damage);

        view.UpdateHealth(
            model.CurrentHealth,
            model.MaxHealth
        );

        if (model.CurrentHealth <= 0)
        {
            view.ShowDeadUI();
        }
    }
}

Presenter가 Model이랑 View를 받아서 사용

장점

view와 model을 명확히 분리가능
UI를 교체하거나 수정하기 쉽다.
presenter를 테스트하기 쉽다.

단점

클래스와 인터페이스가 증가한다.
간단한 UI에는 과할 수 있다.
presenter가 너무 커질 수 있다.

profile
초보 개발자 블로그입니다!

0개의 댓글