"깊이"가 다른 게임개발자 허민영

유저에서 게임까지, 철학에서 코딩까지, 본질을 보는 게임개발

소프트웨어 공학/아키텍처 및 구조론

제4정규화(4NF)의 원칙과 의존성 주입의 구조화

허민영 2025. 4. 9. 18:51

**제4정규화(Fourth Normal Form, 4NF)**는 관계형 데이터베이스 정규화 과정 중 고차원 단계로, 제3정규화 및 BCNF를 만족하는 상태에서 **다치 종속(Multivalued Dependency)**을 제거하는 것을 목표로 한다. 다치 종속이란, 한 테이블이 두 개 이상의 서로 독립적인 다대다 관계를 동시에 포함할 때 발생한다. 이러한 구조는 각 관계가 서로 독립적임에도 불구하고 하나의 테이블에 결합되어 있어, 중복과 비효율을 유발하게 된다.

예를 들어 다음과 같은 테이블을 살펴보자.

 

Student Language Hobby
Alice English Piano
Alice English Reading
Alice French Piano
Alice French Reading

이 테이블은 Student가 Language와 Hobby라는 서로 독립적인 두 관계를 동시에 표현하고 있다. 이는 다치 종속 구조이며, 제4정규화를 위반한 상태다. 이를 정규화하려면 각 다대다 관계를 별도의 테이블로 분리해야 한다:

  • Student-Language 테이블
  • Student-Hobby 테이블

이를 통해 각 관계가 독립성을 확보하고, 중복 없이 간결하게 표현된다.


제4정규화와 의존성 주입의 연결점

다치 종속은 "서로 독립적인 요소들이 하나의 구조에 결합되어 생기는 결합도 문제"이다. 객체지향 설계에서도 이와 유사하게, 서로 독립적인 의존성이 하나의 클래스 내부에 직접 결합되어 있을 경우 문제가 발생한다. 이 구조는 클래스 간 결합도를 높이고 테스트를 어렵게 만들며, 확장 시 부작용이 발생할 가능성을 높인다.

이러한 문제를 해결하는 대표적인 접근이 **의존성 주입(Dependency Injection, DI)**이다. 의존성 주입은 클래스가 스스로 의존 객체를 생성하거나 고정된 구현에 의존하지 않고, 외부에서 필요한 객체를 주입받도록 설계함으로써 결합을 약화시키는 기법이다.

즉, 제4정규화가 서로 독립적인 관계를 분리하여 중복을 제거하고 독립성을 확보하는 작업이라면, 의존성 주입은 객체 간 결합을 줄이고 구성 책임을 외부로 위임하여 구조적 유연성을 확보하는 작업이라고 할 수 있다. 이 둘은 목적과 철학에서 매우 유사한 방향성을 가진다.


잘못된 의존 구조 예시: 다치 종속적 설계

public class EnemySpawner : MonoBehaviour
{
    private AudioSource _audioSource;
    private EnemyFactory _enemyFactory;

    void Start()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _enemyFactory = new EnemyFactory(); // 직접 생성
    }

    public void Spawn()
    {
        GameObject enemy = _enemyFactory.CreateEnemy();
        _audioSource.Play();
    }
}

이 클래스는 AudioSource와 EnemyFactory라는 서로 독립적인 두 기능에 동시에 직접 의존하고 있다. 이로 인해 두 기능이 수정되거나 대체되어야 할 때, 이 클래스 전체가 영향을 받는다. 이는 데이터베이스에서 다치 종속이 하나의 테이블에 몰려 있을 때와 비슷한 구조적 문제를 유발한다.


리팩토링: 의존성 주입을 통한 독립성 확보

제4정규화의 철학을 반영하여, 각 기능을 외부에서 주입받는 방식으로 설계 구조를 분리할 수 있다.

public class EnemySpawner
{
    private readonly IEnemyFactory _enemyFactory;
    private readonly IAudioPlayer _audioPlayer;

    public EnemySpawner(IEnemyFactory enemyFactory, IAudioPlayer audioPlayer)
    {
        _enemyFactory = enemyFactory;
        _audioPlayer = audioPlayer;
    }

    public void Spawn()
    {
        GameObject enemy = _enemyFactory.CreateEnemy();
        _audioPlayer.Play();
    }
}

public interface IEnemyFactory
{
    GameObject CreateEnemy();
}

public interface IAudioPlayer
{
    void Play();
}

이제 EnemySpawner는 명확히 정의된 역할만 수행하며, 각 기능의 구현은 외부에서 주입되므로 서로 독립적으로 교체가 가능하다. 예를 들어 테스트 환경에서는 실제 오디오 출력이 아닌 더미 객체를 주입할 수 있고, 팩토리 방식도 런타임에 따라 쉽게 변경 가능하다.


결론

제4정규화는 다치 종속을 제거하여 관계 간 독립성과 구조적 일관성을 확보하는 데이터 설계 원칙이다. 객체지향 프로그래밍에서의 의존성 주입은 이 철학을 소프트웨어 구조로 확장한 방식이라 할 수 있다. 의존성이 얽혀 있는 구조를 정제하고, 책임을 분리하며, 결합도를 낮추는 설계 방식은 유지보수성과 확장성을 갖춘 시스템을 구축하는 데 필수적이다. Unity 개발 환경에서도 생성자 주입, 인터페이스 기반 주입 등의 기법을 활용하면, 각 기능을 독립적으로 관리하면서도 조합 가능한 유연한 아키텍처를 설계할 수 있다.