Quick Comparison
“불을 쏜다”, “비행한다”, “체력이 있다”처럼 독립적으로 붙고 빠지는 기능은 component composition에 맞습니다. “특정 collider의 한 종류”처럼 안정된 is-a 관계와 공통 lifecycle 구현은 얕은 class inheritance가 읽기 쉽습니다. interface는 구현 재사용이 아니라 caller가 요구하는 capability 계약입니다.
| 변화의 성격 | 먼저 선택 | 책임 |
|---|---|---|
| feature 조합이 계속 늘어남 | Unity Component 또는 plain C# composition | owner가 의존 component를 연결·정리 |
| 종류가 안정적이고 공통 구현이 큼 | 얕은 base class | base의 invariant와 override 규칙 |
| 여러 type에 필요한 행동 | 작은 interface | caller가 필요한 member만 의존 |
| 수천 entity의 data-oriented update | ECS 검토 | authoring·runtime data 변환과 job 규칙 |
public interface IDamageable
{
void TakeDamage(int amount);
}
public sealed class Enemy : MonoBehaviour, IDamageable
{
[SerializeField] private Health health;
public void TakeDamage(int amount) => health.Apply(amount);
}조합 폭발을 막는 기준
FlyingFireEnemy, FlyingIceEnemy, GroundFireEnemy처럼 feature 조합마다 subclass를 만들기 시작하면 새 mechanic이 type tree 전체를 늘립니다. 이런 차이는 base type의 본질보다 movement·attack·targeting·health 같은 역할의 조합이므로 component로 나누는 편이 바뀌는 feature를 국소화합니다.
컴포지션도 자동으로 느슨해지지 않습니다. Weapon이 임의의 Health, Inventory, UI를 GetComponent로 찾아 직접 고치면 dependency가 숨습니다. 필요한 reference는 Inspector, constructor, bootstrapper 중 하나에서 owner가 연결하고, component가 요구하는 collaborator와 lifecycle을 RequireComponent, serialized field validation, small interface 같은 방식으로 드러냅니다.
상속과 interface의 역할
C# class는 하나의 direct base class만 상속하지만 여러 interface를 구현할 수 있습니다. base class는 실제 공통 state·template method·protected helper가 있고 subtype 규칙이 안정적일 때 사용합니다. interface는 IDamageable, ITargetable처럼 caller가 필요로 하는 capability만 표현합니다. Unity uGUI와 UI Toolkit은 다른 UI type hierarchy이므로 UIElement -> Button / Slider 같은 예를 일반 Unity 상속 관계로 쓰지 않습니다.
상속을 골랐다면 subtype이 base가 약속한 입력·출력·lifecycle을 깨지 않는지 test합니다. 컴포지션을 골랐다면 feature 간 message·event·direct call 중 어느 연결을 쓰는지와 disable·destroy 때 unsubscribe owner를 정합니다.
Unity에서 조립하기
Unity GameObject + Component는 component composition을 지원하지만 component 수가 많아질수록 execution order와 data ownership이 중요해집니다. authoring setting은 Inspector 또는 ScriptableObject에, instance state는 runtime component에 두고 shared asset을 actor state처럼 바꾸지 않습니다. ECS는 같은 문제를 다른 실행·data layout으로 푸는 선택지이므로 MonoBehaviour component를 많이 쓴다는 이유만으로 옮기지 않습니다.
상속을 피하려고 component를 무한히 쪼개거나, interface를 만들고 singleton을 다시 찾으면 복잡도만 이동합니다. 기능 변화인지 종류 변화인지, 누가 reference와 lifecycle을 소유하는지부터 결정하세요.
참고 링크
2 sources