용로그
article thumbnail

우리가 프로그래밍을 하다 보면 싱글톤의 유혹을 버티지 못할 때가 있다. 싱글톤 자체의 의도는 메모리 절약이라는 좋은 근거가 될 수 있지만, 그로 인해 발생하는 문제점 또한 많은 편이다.

 

그래서 이번 글에서는 싱글톤의 장단점과 어떤 상황에서 안티 패턴으로 작용할 수 있는지를 알아보자.


안티패턴?
설계상의 문제로 인해 소프트웨어 시스템이 예상대로 작동하지 않거나 유지보수하기 어렵게 만드는 디자인 패턴이다.

 

싱글톤 패턴이란?


싱글톤은 객체지향 프로그래밍(OOP)에서 사용되는 디자인 패턴 중 하나다. 이 패턴은 Application에서 단 하나의 인스턴스만을 생성하고, 이 인스턴스를 전역에서 사용할 수 있도록 한다.

 

public class Singleton {
    private static String instance = null;

    private Singleton() {
        this.instance = "Singleton";
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

싱글톤 패턴을 구현하는 가장 간단한 방법이며, 가장 많이 사용하는 코드이기도 하다. 위 코드에서는 Singleton 클래스의 유일한 인스턴스를 보증하기 위해 기본 생성자의 접근제어자를 private으로 만들어 놓은 모습이다.

 

또한 이렇게 구현된 싱글턴의 인스턴스에 접근하기 위해 getInstance라는 정적 팩토리 메서드를 구현했다.

 

싱글톤 패턴 장점


그럼 싱글톤 패턴을 사용하면 어떠한 부분에서 이득일까? 싱글톤 패턴은 아래와 같은 이유로 많은 경우에 유용하게 사용된다.

 

접근성

싱글톤은 전역적으로 접근 가능한 유일한 인스턴스를 제공하기 때문에 객체를 생성하고 유지보수하는 데에 필요한 코드 양을 줄일 수 있다. 객체를 생성하는 데 필요한 코드를 모든 클라이언트 클래스에서 구현하지 않아도 된다.

 

예를 들면 다수의 스레드가 공유하는 리소스를 처리하는 경우, 싱글톤 패턴을 사용하여 해다 리소스에 대한 접근을 동기화하고, 쉽게 관리할 수 있다. 이렇게 구현된 싱글톤 패턴은 코드를 단순화하고 복잡도를 줄일 수 있다.

 

메모리 절약

여러 인스턴스를 생성할 때 마다 메모리와 CPU 자원을 소비하게 된다. 싱글톤 패턴은 단 하나의 인스턴스를 생성하므로 자원을 효율적으로 사용할 수 있다.

 

데이터 일관성 보장

여러 인스턴스를 생성하게 되면, 각각의 인스턴스가 서로 다른 데이터가 가질 수 있지만, 싱글턴 패턴으로 인스턴스를 생성할 시 단 하나의 인스턴스만 생성하기에 인스턴스의 데이터에 대한 일관성을 보장할 수 있다.

 

싱글톤 패턴 단점


테스트의 어려움

싱글턴 인스턴스는 어디서든지 접근할 수 있는 객체다. 이 인스턴스가 다른 객체와 의존성이 있는 경우에는 해당 객체를 테스트하기 어렵다. 이것은 특히 싱글톤 인스턴스가 상태를 가지는 경우에는 더 문제가 될 수 있다.

 

상태를 가진다는 것은 인스턴스 내부의 데이터가 변경될 수 있다는 것이다. 테스트케이스마다 인스턴스의 상태가 변경되기 때문에 다음에 이루어질 테스트에 영향을 끼칠 수 있다.

 

멀티스레드 환경에 문제

멀티스레드 환경에서 여러 개의 스레드가 싱글톤 인스턴스에 동시에 접근할 경우 인스턴스가 여러 번 생성될 수 있다. 이 문제를 해결하기 위해서는 동기화 처리를 해주어야하는데, 이 처리 자체가 성능에 영향을 미칠 수 있다.

 


Q. 자바에서 동기화 처리 하는 방법?
A. 멀티스레드 환경에서 동기화 처리를 하고 싶다면 synchronized 키워드를 사용하여 해결할 수 있다. 아래는 synchronized를 사용하여 Singleton 객체를 생성하는 코드의 예시다.

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // private constructor
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}​


하지만 synchronized 키워드의 원리를 살펴보면 여러개의 스레드가 하나의 메서드에 접근하면, 다른 스레드들은 해당 메서드를 사용하기 위해 대기하게 되는데, 이를 OS에서는 점유와 대기라고 하며, 락을 획득하는 것은 성능 저하의 원인이 될 수 있다.

 

객체 지향적이지 못하다.

싱글톤 패턴은 객체 지향 프로그래밍의 원칙(SOLID)들 중에 하나인 DIP(의존성 역전 원칙)를 위배한다. 싱글톤 패턴을 사용하면 싱글톤 클래스와 다른 클래스의 결합도가 높이지기 때문에 싱글톤 클래스의 수정이 일어나면 다른 클래스들에게까지 영향이 가기 때문이다.

 

싱글톤 패턴이 안티 패턴이 될 수 있는 상황


싱글톤 패턴은 각 상황에 대해 장단점이 있기 때문에 항상 안티 패턴이라고 할 수는 없다. 따라서 안티 패턴이다 아니다로 구분하는 것 보단, 안티 패턴으로 작용될 수 있는 상황에 대해서 알아보자.

 

상속이 필요할 때

싱글톤은 자신만이 객체를 생성할 수 있도록 생성자를 private으로 제한한다. 하지만 상속을 통해 다형성을 적용하기 위해서는 다른 기본생성자가 필요하므로 객체지향의 장점을 적용할 수 없다.

 

테스트의 완전한 격리가 필요할 때

싱글톤 패턴은 전역 상태를 유지하는데 사용될 수 있다. 하지만 전역 상태를 사용하면 다른 모듈과의 의존성이 높아져서 테스트와 코드를 이해하기 어려워진다.

 

멀티스레드 환경에서 안전하지 않을 때

위에서 언급했다시피 싱글톤 패턴은 멀티스레드 환경에서 안전하지 않을 수 있다. 동시에 여러 스레드가 싱글톤 객체에 접근하면 의도하지 않은 결과가 발생할 수 있다. 이러한 문제를 해결하기 위해서는 synchronized 또는 volatile 키워드를 사용하여 동기화 처리를 해주어야 한다.

 

객체의 수명 주기가 제어되지 않을 때

싱글톤 패턴은 객체의 수명 주기를 제어하기 어렵다. 객체의 생성과 소멸을 개발자가 직접 제어할 수 없기 때문에, 객체 생성 시점과 소멸 시점에 대한 유연성이 떨어진다.

 

단일 책임 원칙을 위배할 때

싱글톤 패턴은 하나의 클래스가 여러 가지 책임을 담당하게 되면서 단일 책임 원칙(SRP)을 위배할 수 있다. 싱글톤 클래스가 생성, 관리, 전역 상태 유지 등 여러 가지 책임을 담당하게 되면, 클래스의 크기가 커지고 유지보수가 어려워진다.

 

의존성 주입(Dependency Injection)을 사용할 때

의존성 주입 패턴은 객체 간의 의존성을 외부에서 주입하여 객체 간의 결합도를 낮추는 패턴이다. 싱글톤 패턴은 의존성 주입 패턴과 호환되지 않는다. 싱글톤 클래스를 사용하는 다른 클래스들은 싱글톤 객체에 직접 의존하게 되어 결합도가 높아진다.

 

만약 자신이 싱글톤 패턴 도입을 위해 고민하고 있을 때 위와 같은 상황에 처해있다면 다른 방법으로 해결하는 것을 고민해보자.

profile

용로그

@용로그

벨덩보단 용덩 github.com/wonyongChoi05