티스토리 뷰
🧙♂️ God Object
전지전능한 오버시어, 고장나면 세계종말(?)
'갓 오브젝트'는 특정 오브젝트/스크립트가 한 씬(플로우, 시퀀스 등)에서 너무 많은 역할/책임을 가지게 되는 것으로 관리 혹은 오류에 취약해질 수 있는 상태가 될 수 있다.
유니티 게임 개발을 예로들면, 'GameManger' 클래스를 만들고 모든 흐름 제어를 'GameManager'가 하는 것과 같다.
이는 관리 차원에서는 쉬울 수 있다. 게임매니저가 허브의 역할을 하여 디버깅이 단순하고 각 매니저, 컨트롤러들의 독립성도 지킬 수 있다.
단, 이러한 경우 "기능 추가 = 게임매니저 코드 증가"로 쌓이게 되고 분기 처리와 책임 분리가 모호해질 수 있다. 또한 간단한 작업도 게임매니저를 통과해야하는 불필요한 절차가 반강제적으로 이루어질 수 있다.
✅ 장점 | 설명 |
일관된 흐름 제어 | 전체 게임 플로우를 GameManager가 조율하여, 버그나 로직 흐름 파악이 쉬움 |
디버깅 단순화 | 이벤트의 시작점과 연결된 액션이 GameManager에 집중되어 추적이 쉬움 |
낮은 결합도 | 매니저 간 직접 참조 없이 GameManager를 통해 통신하므로 유지보수에 유리 |
❌ 단점 | 설명 |
God Object 위험 | GameManager가 너무 많은 책임을 지게 되어 거대 클래스가 될 가능성 있음 |
확장성 저하 | 기능이 늘어날수록 GameManager 코드가 복잡하고 무거워짐 |
유연성 부족 | 단순한 상호작용조차 GameManager를 거치면 오히려 구조가 불편해질 수 있음 |
💡 EventHub (이벤트 버스) 패턴
가장 간단하면서도 느슨하게 연결할 수 있는 방법.
각 컨트롤러가 사소한 이벤트를 주고/받고 싶을 때, 중간에 정적 이벤트 허브를 둬서 연결하는 방식이다.
구현 예시
public static class EventHub {
public static Action<MonsterController> OnMonsterDied;
}
// MonsterController
public void Die() {
Debug.Log("Monster died");
EventHub.OnMonsterDied?.Invoke(this);
}
// BattleUIController.cs
private void OnEnable() {
EventHub.OnMonsterDied += ShowDeathEffect;
}
private void OnDisable() {
EventHub.OnMonsterDied -= ShowDeathEffect;
}
private void ShowDeathEffect(MonsterController monster) {
Debug.Log($"{monster.name} 사망");
}
✅ 장점 | ❌ 단점 |
컨트롤러 간 직접 참조 없음 → 낮은 결합도 | 구독 해제 누락 시 메모리 누수 가능 |
구현이 단순하고 직관적 | 어디서 이벤트가 발생했는지 추적이 어려움 |
유지보수성, 유연성 높음 |
💡 인터페이스 + DI (의존성 주입)
조금 더 정형화된 구조를 원한다면, 인터페이스를 통해 의존성을 주입하는 방법도 좋다.
초기화 타이밍에서 명시적으로 연결을 해주는 방식이다.
구현 예시
// IMonsterEventReceiver.cs
public interface IMonsterEventReceiver {
void OnMonsterDied(MonsterController monster);
}
// MonsterController.cs
private IMonsterEventReceiver receiver;
public void SetReceiver(IMonsterEventReceiver receiver) {
this.receiver = receiver;
}
public void Die() {
receiver?.OnMonsterDied(this);
}
// BattleUIController.cs
public class BattleUIController : MonoBehaviour, IMonsterEventReceiver {
public void OnMonsterDied(MonsterController monster) {
Debug.Log($"{monster.name} 사망");
}
}
// GameInitializer.cs
public class GameInitializer : MonoBehaviour {
[SerializeField] private MonsterController monster;
[SerializeField] private BattleUIController battleUI;
private void Awake() {
monster.SetReceiver(battleUI); // 수동 주입
}
}
✅ 장점 | ❌ 단점 |
의존성 명확 → 테스트/리팩터링 용이 | 외부에서 반드시 주입해줘야 함 (타이밍 주의) |
어디서 연결되는지 명확해서 추적 쉬움 | 코드가 살짝 길어짐 |
유닛 테스트에 강함 |
🎯 결론
항목 | EventHub | 방식인터페이스 + DI |
결합도 | 낮음 | 낮음 (더 명확함) |
디버깅 | 어려움 | 쉬움 |
구현 난이도 | 낮음 | 중간 |
테스트 용이성 | 보통 | 높음 |
확장성 | 높음 | 높음 |
🧠 마무리
- 게임의 흐름 전체를 조율하는 이벤트는 GameManager 또는 EventBus를 통해 처리하는 것이 좋다.
- 기능 단위 컨트롤러 간의 이벤트 처리는 EventHub 혹은 인터페이스 + DI로 처리하면 깔끔.
- 정말 고급 구조로 가고 싶다면, Zenject 같은 DI 컨테이너를 활용해도 좋다.
- DI패턴도 너무 난발하면 안좋으니 필요에 따라 명확히 사용할것.
'개발 > Unity' 카테고리의 다른 글
[Unity] 유니티 최적화 체크 리스트 (0) | 2025.05.15 |
---|---|
[Unity] 사용하지 않는 리소스의 메모리 관리 (0) | 2024.05.10 |
[Unity] SOLID 원칙과 게임 개발 (1) | 2024.03.31 |
[Unity] Custom Editor (0) | 2023.09.03 |
[Unity] 인스펙터 레이아웃 생성 과정 (0) | 2023.09.03 |