얼마전 페이스북에서 많은 Java 프로그래머들이 당연시하게 기록자(logger)를 정적(static)으로 사용하는 것을 비판했는데 비슷한 주장을 하는 다른 분의 글에서 기록자는 인스턴스 범위(scope)에 있지 않다는 반론을 발견했다. 나는 반론 제기자에게 그것은 논점을 벗어나니 DIP(dependency inversion principle)을 공부하라고 조언했다. 아쉽지만 반응에 의하면 아마 내 조언은 무시된 것 같았다.

 

응용프로그램 또는 프로세스와 생명주기를 함께하는 단일 서비스 인스턴스는 상태를 가지지 않거나 공유 가능한 비싼 자원을 관리하거나 하는 경우에 유용하다. 그런데 이 하나의 서비스 인스턴스는 반드시 정적 변수에 담겨야할까? 아니다. 그렇게 하지 말라. 정적 변수에 담긴 인스턴스는 명시적으로 공유되는 자원임을 의미한다. 이런 자원을 사용하는 클라이언트 코드는 사용하기 어렵고 재사용하기 어렵고 테스트하기 어렵다.

그렇다면 클라이언트 시스템은 싱글턴 서비스 인스턴스에 어떻게 접근해야 할까? 고민할 것 없다. 다른 서비스와 마찬가지로 생성자를 통해 주입하라. 다음 예제 코드를 살펴보자.

using System;

public class SingletonService
{
    public string GetMessage() => "Hello World";
}

public class ClientModule
{
    private readonly SingletonService _service;

    public ClientModule(SingletonService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public void PrintMessage() => Console.WriteLine(_service.GetMessage());
}

public class IocContainer
{
    private readonly Func<ClientModule> _factory;

    public IocContainer(Func<ClientModule> factory)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    }

    public ClientModule GetModule() => _factory.Invoke();
}

public class Program
{
    public static void Main(string[] args)
    {
        var singleton = new SingletonService();
        var container = new IocContainer(() => new ClientModule(singleton));
        Run(container);
    }

    private static void Run(IocContainer container)
    {
        for (int i = 0; i < 5; i++)
        {
            ClientModule module = container.GetModule();
            Execute(module);
        }
    }

    private static void Execute(ClientModule module) => module.PrintMessage();
}

어떤가? 응용프로그램은 SingletonService 인스턴스를 단 하나만 가지지만 코드 어디에도 정적 필드는 존재하지 않는다. ClientModule은 API를 통해 SingletonService에의 의존성이 명시적으로 드러나고 이 의존성이 해결되지 않으면 개체를 생성할 수 없도록 엄격히 설계되었으며 테스트하기 쉽다.