[F-Lab 66해빗 페이백 챌린지 ]

[F-Lab 모각코 페이백 20일차] 싱글톤 패턴, 팩토리 패턴

everydeveloper 2023. 6. 5. 23:57

학습 목표

  • 디자인 패턴
  • 싱글톤
  • 팩토리

 

TIL

  • 싱글톤
    • Lazy Initialization(지연 초기화
    • Bill Pugh Singleton (Initialization on Demand Holder idiom) (Holder)(내부 클래스)
  • 팩토리
    • 단순 팩토리 패턴
    • 팩토리 메서드 패턴
    • 추상 팩토리 패턴

 

싱글톤

 

싱글톤 디자인 패턴은 특정 클래스의 인스턴스가 하나만 생성되도록 보장하는 패턴입니다. 이 패턴은 전역 변수를 사용하지 않고도 클래스의 인스턴스를 전역적으로 접근 가능하게 만들기 때문에 유용합니다.

이 패턴은 특히 자원이 제한적인 상황에서 유용하며, 여러 곳에서 동일한 객체에 접근해야 할 때도 사용됩니다. 예를 들어, 로그 파일 작성기, 데이터베이스 연결, 파일 시스템 등의 작업에서 싱글톤 패턴을 사용할 수 있습니다.

싱글톤 패턴을 사용하는 방법은 여러 가지가 있지만, 대표적으로는 다음과 같이 사용할 수 있습니다:

  1. 클래스의 생성자를 private로 만들어서 외부에서 인스턴스를 생성하지 못하도록 합니다.
  2. 그 클래스 안에 정적 메서드를 만들어 그 메서드가 유일한 인스턴스를 생성하고 반환하도록 합니다. 이 메서드는 보통 getInstance라는 이름을 갖습니다.
  3. 유일한 인스턴스를 저장할 private static 변수를 만듭니다. 이 변수는 getInstance 메서드에서 사용됩니다.

이런 식으로, 싱글톤 패턴을 사용하면 클래스의 인스턴스가 하나만 생성되도록 보장할 있습니다. 그러나 패턴은 적절히 사용해야 합니다. 싱글톤 객체가 너무 많은 역할을 담당하거나, 객체 간의 의존성을 높여서 코드의 유연성을 떨어뜨릴 있기 때문입니다. 따라서 싱글톤 패턴을 사용할 때는 장단점을 고려해야 합니다.

 

트레이드 오프 (Trade-off)

 

"트레이드 오프(Trade-off)"는 일반적으로 어떤 결정이나 선택이 다른 것들에 미치는 영향을 설명하는 데 사용되는 용어로, 한 가지를 얻기 위해 다른 것을 포기해야 하는 상황을 의미합니다. 이는 모든 프로그래밍 언어와 관련된 고려 사항이며, 자바도 예외는 아닙니다.

자바에서의 트레이드 오프는 여러 가지 형태로 나타날 수 있습니다. 예를 들어:

  1. 성능 대 메모리: 알고리즘 또는 자료구조를 선택할 때, 메모리 사용을 최소화하면서도 원하는 성능을 달성하려는 상황에서 트레이드 오프가 발생할 수 있습니다. 예를 들어, 배열은 메모리를 효율적으로 사용하지만, 리스트는 동적으로 크기를 조절할 수 있어서 더 유연합니다.
  2. 유지보수성 대 성능: 가끔은 성능을 극대화하기 위해 코드를 복잡하게 만드는 경우가 있습니다. 이런 경우, 코드가 복잡해져서 유지보수가 어려워질 수 있습니다. 따라서 코드의 성능을 높이는 것과 코드의 가독성 및 유지보수성을 유지하는 것 사이에 트레이드 오프가 발생할 수 있습니다.
  3. 개발 시간 대 성능: 빠르게 개발하려면 성능이 떨어지는 코드를 작성할 수도 있습니다. 반대로, 성능을 최적화하려면 더 많은 시간을 투자해야 할 수 있습니다. 이런 상황에서는 개발 시간과 성능 사이에 트레이드 오프가 있을 수 있습니다.

따라서 자바에서 트레이드 오프란, 하나의 목표를 달성하기 위해 다른 목표를 일정 부분 포기하는 상황을 의미합니다. 이러한 트레이드 오프를 이해하고 관리하는 것은 효과적인 소프트웨어 개발의 핵심 요소입니다.

 

싱글톤 패턴의 인스턴스 생성과 일반 인스턴스 생성 차이

싱글톤 패턴과 일반 인스턴스 생성은 다음과 같이 다릅니다:

  1. 싱글톤 패턴: 싱글톤 클래스는 JVM에 로드될 때 싱글톤 인스턴스를 생성합니다. 이는 static 필드를 통해 이루어집니다. 이 필드는 클래스 로드 시점에 한 번만 초기화되므로 싱글톤 인스턴스 또한 한 번만 생성됩니다. 이후에는 이 이미 생성된 인스턴스에 접근할 수 있습니다.
  2. 일반 인스턴스 생성: 일반적인 클래스 인스턴스는 new 키워드를 사용하여 명시적으로 생성됩니다. 이는 클래스가 로드된 후에 일어나는 과정이며, 필요에 따라 여러 번 실행될 수 있습니다. 이 때마다 새로운 인스턴스가 메모리에 할당됩니다.

따라서 싱글톤 패턴은 클래스 로드 시점에 번만 인스턴스를 생성하여 이를 재사용하며, 일반 인스턴스 생성은 new 키워드를 통해 필요할 때마다 인스턴스를 생성하는 것이 주요 차이점입니다.

 

싱글톤 패턴 종류

싱글톤 패턴은 여러 가지 방법으로 구현할 수 있으며, 각 방법은 특정 시나리오에 따라 장단점이 있습니다. 여기서는 주로 사용되는 싱글톤 패턴의 종류를 몇 가지 소개하겠습니다:

  1. Eager Initialization(즉시 초기화): 이 방법은 클래스가 로딩될 때 싱글톤 인스턴스를 미리 생성하는 방식입니다. 이 방법은 구현이 쉽지만, 클래스가 로딩되는 시점에 인스턴스가 무조건 생성되므로 필요하지 않을 때에도 인스턴스가 생성되는 단점이 있습니다.
  2. Lazy Initialization(지연 초기화): 이 방법은 싱글톤 인스턴스가 필요한 시점에 생성하는 방식입니다. 이 방법은 인스턴스를 필요한 시점에만 생성하므로 자원을 효율적으로 사용할 수 있는 장점이 있지만, 멀티 스레드 환경에서는 동기화 문제가 발생할 수 있습니다.
  3. Thread Safe Singleton(스레드 안전 싱글톤): 이 방법은 멀티 스레드 환경에서도 인스턴스 생성을 보장하는 방식입니다. 하지만 동기화(synchronization)는 연산 비용이 발생하므로 성능에 영향을 줄 수 있습니다.
  4. Double Checked Locking/Initialization(이중 확인 잠금/초기화): 이 방법은 'Lazy Initialization'과 'Thread Safe Singleton'의 장점을 모두 가져다 쓴 방식입니다. 두 번의 확인을 통해 동기화 비용을 최소화하면서도 스레드 안전성을 보장합니다.
  5. Bill Pugh Singleton(Initialization on Demand Holder idiom): 이 방법은 JVM의 클래스 로더 매커니즘과 클래스의 로드 시점을 이용하여, 내부 클래스를 이용한 지연 초기화를 수행하고 스레드 안전성을 보장하는 방식입니다. 이 방법은 일반적으로 가장 널리 추천되는 싱글톤 구현 방법입니다.
  6. Enum Singleton: 이 방법은 Java 1.5 이후부터 사용 가능한 방식으로, 열거형(Enum)을 이용해 싱글톤을 구현합니다. 이 방법은 간결하며, 직렬화/역직렬화와 관련된 문제를 자동으로 해결해 줍니다. 추가로 리플렉션을 통한 공격에도 안전합니다.

각 방법은 특정 상황과 요구 사항에 따라 적절하게 선택해야 합니다. 예를 들어, 

 

- 애플리케이션이 시작할 때 많은 자원을 사용하거나 성능에 민감하지 않은 경우 'Eager Initialization'을 사용할 수 있습니다.

- 애플리케이션이 싱글톤 객체를 꼭 필요로 하지 않거나 객체 생성 비용이 큰 경우 'Lazy Initialization'을 사용할 수 있습니다.

- 애플리케이션 환경이 멀티 스레드인 경우, 'Thread Safe Singleton'이나 'Double Checked Locking'을 사용할 수 있습니다.

- 코드의 간결성과 성능이 중요하고, 스레드 안전도 보장해야 하는 경우 'Bill Pugh Singleton' 방식을 사용할 수 있습니다.

- 직렬화와 역직렬화가 필요하거나, 리플렉션 공격에 대비해야 하는 경우 'Enum Singleton'을 사용할 수 있습니다.

 

필요한 요구 사항과 환경에 따라 가장 적합한 방식을 선택하는 것이 중요합니다.

 

가장 많이 쓰이는 싱글톤 패턴

 

자바에서 가장 널리 사용되고 추천되는 싱글톤 패턴은 "Initialization on Demand Holder idiom" 방식, 즉 "Bill Pugh Singleton" 방식입니다. 이 방식은 JVM의 클래스 로딩 매커니즘과 클래스의 로드 시점을 이용하여 스레드 안전성을 보장하면서도 지연 초기화를 할 수 있는 방식으로, 성능적으로도 우수하다고 평가받습니다.

"Bill Pugh Singleton" 방식의 코드는 아래와 같습니다:

public class Singleton {
    private Singleton() {
        // Private Constructor
    }

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

위 코드에서 볼 수 있듯이, 내부 클래스 Holder는 외부 클래스 Singleton의 getInstance() 메서드가 처음 호출될 때 로딩되며, 그 때 Singleton 인스턴스가 생성됩니다. 이렇게 하면 멀티스레드 환경에서도 하나의 Singleton 인스턴스만 생성되는 것을 보장할 수 있습니다.

다른 널리 사용되는 방식은 "Enum Singleton" 방식입니다. 방식은 간결하고, 직렬화/역직렬화 문제와 리플렉션 공격에 안전한 장점이 있습니다.

 

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // Do something
    }
}

하지만 Enum Singleton 방식은 상속을 사용할 없고, Enum 클래스의 특성상 구현이 제한적일 있어, 실제 애플리케이션의 요구사항과 상황에 따라 적절한 방식을 선택해야 합니다.

 

설명 만으로는 이해가 안되어서 GTP4에 질문을 해보았고 아래는 몇번의 대화 내용입니다.ㅎ

 

Q.

Initialization on demand holder idiom 싱글톤 패턴에서 내부 클래스에 new 생성자를 쓰면  JVM 클래스 초기화 과정에서 보장되는 원자적 특성을 이용하는 것이라고 하는데 무슨 의미야?

 

A.

"Initialization on demand holder idiom"이라는 용어는 싱글토인 패턴에서 특별한 방식으로 사용되며, Java의 클래스 로드 매커니즘과 클래스 초기화 과정의 특성을 이용합니다.

 

이 패턴에서 '원자적 특성'은 Java의 클래스 초기화 과정이 한 번만 실행되며, 이 과정이 스레드에 안전하다는 것을 의미합니다. 즉, 두 개 이상의 스레드가 동시에 클래스를 초기화할 수 없습니다. 이러한 특성은 JVM에서 자동으로 보장되므로 개발자가 별도의 동기화 처리를 할 필요가 없습니다.

 

"Initialization on demand holder idiom" 패턴은 이러한 특성을 이용하여 지연 초기화(lazy initialization)를 하면서도 동시성 문제 없이 싱글톤 인스턴스를 생성합니다. 이 패턴은 내부 클래스를 사용하여 싱글톤 인스턴스를 보유합니다.

 

다음은 패턴의 구현 예제입니다:

 

public class Singleton {
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

위 코드에서 `Singleton` 클래스가 로드되었을 때 `Holder` 클래스는 로드되지 않습니다. `Holder` 클래스는 `getInstance()` 메서드가 처음 호출될 때 로드되며, 이때 싱글톤 인스턴스가 생성됩니다. `Holder` 클래스의 초기화 과정은 한 번만 실행되며, 여러 스레드에서 동시에 `getInstance()`를 호출하더라도 싱글톤 인스턴스는 한 번만 생성됩니다.

 

따라서, 패턴은 클래스 초기화 과정의 원자적 특성을 이용하여 동기화 없이도 스레드 안전한 싱글톤을 구현합니다.

 

Q.

여기서 사용한 클래스 초기화 과정의 원자적 특성을 좀 더 자세히 설명해줘

 

A.

'원자적'이라는 용어는 여러 단계로 이루어진 연산이 하나의 단계로 보이는 특성을 의미합니다. 원자적 연산은 중간 단계에서 중단되지 않으며, 여러 스레드가 동시에 원자적 연산을 실행하더라도 간섭 없이 안전하게 실행됩니다.

클래스 초기화 과정의 '원자적 특성'은 JVM이 클래스를 초기화하는 과정이 원자적 연산으로 처리된다는 것을 의미합니다. 즉, 클래스 초기화 과정은 한 번에 한 스레드만이 수행할 수 있으며, 한 번 클래스가 초기화되면 다른 스레드에서 이를 다시 수행할 수 없습니다.

JVM의 클래스 초기화 과정은 다음 단계를 포함합니다:

1. 로드: 클래스 파일(.class)이 JVM에 읽혀지며, 클래스의 바이트코드가 메모리에 로드됩니다.
2. 링크: 검증, 준비, 해석 등의 단계를 거쳐 클래스를 실행할 준비를 합니다. 준비 단계에서 정적 필드가 기본값으로 초기화됩니다.
3. 초기화: 정적 변수가 프로그램에서 주어진 값으로 초기화되고, 정적 초기화 블록이 실행됩니다.

이러한 클래스 초기화 과정이 원자적으로 수행되므로, 한 번 클래스가 초기화되면 다시 초기화되지 않습니다. 이러한 특성을 이용하면 동기화를 사용하지 않고도 스레드 안전한 싱글톤을 구현할 수 있습니다.

 

 

Q.

아 그러면 싱글톤 패턴을 구현하는 여러가지 방법 중에서 쓰래드 lazy 초기화를 사용하는 경우는 대개 자바 애플리케이션이 JVM에서 기동하기 시작하면서 클래스 로드가 자동으로 되면서 실행 되는 반면 holder 의한 초기화는 내부 클래스이기 떄문에 따로 해당 클래스를 호출 하기 전까지는 클래스 로드 과정이 진행되기 않기 때문에, 그리고 호출 될때는 클래스 로드 과정 자체가 쓰래드 안전이기 때문에 하나의 인스턴스만 생성이 보장된다는 거지?

 

A.

네, 정확합니다. "Initialization on demand holder idiom" 패턴은 Java의 클래스 로딩 메커니즘과 초기화 과정의 원자성을 이용합니다.

이 패턴에서는 내부 static 클래스(`Holder` 클래스)가 실제 싱글톤 인스턴스를 갖게 됩니다. 이 내부 클래스는 `getInstance()` 메소드가 처음 호출될 때 JVM에 의해 로드되고 초기화됩니다.

이 과정은 JVM에 의해 원자적으로 처리되므로, 여러 스레드가 동시에 `getInstance()` 메소드를 호출하더라도 내부 클래스는 단 한 번만 초기화되며, 따라서 싱글톤 인스턴스도 단 한 번만 생성됩니다.

이렇게 하면 lazy initialization(처음 사용될 때까지 인스턴스 생성을 늦춤)과 thread-safety(다중 스레드 환경에서의 안정성)를 동시에 달성할 수 있습니다. 이는 별도의 동기화 처리 없이도 스레드 안전한 싱글톤을 구현할 수 있게 해주는 강력한 방법입니다.

 

싱글톤 패턴 사용 이유

싱글톤 디자인 패턴은 몇 가지 이유로 사용됩니다:

1. 유일한 인스턴스: 싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되는 것을 보장합니다. 따라서 전역에서 접근 가능한 유일한 인스턴스를 가질 수 있습니다. 예를 들어, 시스템 설정, 로거, 드라이버 객체, 캐시, 스레드 풀, 데이터베이스 연결 등의 리소스를 공유하려 할 때 유용하게 사용됩니다.

2. 제어된 리소스 접근: 싱글톤 패턴은 공유 리소스에 대한 접근을 제어할 수 있습니다. 인스턴스가 하나만 있으므로 여러 클라이언트가 동일한 리소스에 동시에 접근하는 것을 효과적으로 제어할 수 있습니다.

3. 지연 초기화 (Lazy Initialization): 싱글톤 패턴은 인스턴스 생성을 지연시킬 수 있습니다. 즉, 인스턴스가 필요할 때까지 생성을 연기할 수 있습니다. 이는 초기 시작 시간을 단축시키고, 리소스 사용량을 최소화하는데 도움이 됩니다.

4. 전역 접근 포인트 제공: 싱글톤 패턴은 생성된 객체에 대한 전역 접근 포인트를 제공합니다. 이는 객체를 사용하는 클라이언트 코드 측에서 편리성을 제공합니다.

하지만 싱글톤 패턴을 사용할 때는 주의해야할 점도 있습니다. 예를 들어, 멀티스레드 환경에서 적절하게 동기화하지 않으면 싱글톤이 보장되지 않을 수 있습니다. 또한, 싱글톤 인스턴스가 너무 많은 작업을 하거나 상태 정보를 과도하게 유지하게 되면 시스템에 부정적인 영향을 미칠 수 있습니다. 따라서 싱글톤 패턴은 적절하게 사용해야 합니다.

 

싱글톤 패턴 사용시 주의해야 할 점

싱글톤 패턴을 사용할 때는 다음과 같은 주의사항을 기억해야 합니다.

1. 멀티스레딩 문제: 싱글톤 패턴은 멀티스레딩 환경에서 잘못 관리되면 여러 인스턴스가 생성될 수 있는 문제가 있습니다. 따라서 인스턴스 생성 과정을 적절히 동기화해야 합니다. 

2. 글로벌 상태 공유: 싱글톤은 기본적으로 전역 상태를 공유합니다. 이러한 공유된 상태는 다른 부분의 코드에 의해 예기치 않게 변경될 수 있어 디버깅이 어려울 수 있습니다.

3. 확장성 문제: 상속과 다형성을 통한 클래스 확장이 어렵습니다. 인스턴스가 하나만 필요하다는 제약이 클래스 확장을 제한하기 때문입니다.

4. 객체의 생명주기: 싱글톤 객체는 애플리케이션의 생명주기 동안 계속 존재하기 때문에 메모리 리소스를 계속 차지합니다. 필요 없는 시점에서도 싱글톤 객체는 계속 메모리에 남아 있을 수 있으니 이 점을 고려해야 합니다.

5. 테스트하기 어렵다: 싱글톤 클래스는 종종 테스트하기 어렵습니다. 왜냐하면 싱글톤 클래스는 전역 상태를 가지며, 이 상태가 테스트 코드 간에 유지되어 다른 테스트에 부작용을 줄 수 있기 때문입니다. 이런 문제를 피하기 위해선 mock 객체를 사용하는 등의 방법을 활용해야 할 수 있습니다.

따라서, 이런 이유로 싱글톤 패턴은 유용하지만, 적절하게 사용되어야 합니다. 무분별하게 사용하면 시스템 전체의 결합도를 높이고 유지보수를 어렵게 만드는 원인이 될 수 있습니다.

 

 

 

팩토리 패턴

팩토리 패턴(Factory Pattern)은 객체 지향 프로그래밍에서 객체 생성에 관련된 디자인 패턴 중 하나입니다. 이 패턴은 객체를 생성하는 과정을 서브 클래스에 위임하여 코드의 유연성을 높이는 것을 목표로 합니다.

팩토리 패턴은 크게 세 가지 타입으로 나눌 수 있습니다: 단순 팩토리(Simple Factory), 팩토리 메서드(Factory Method), 추상 팩토리(Abstract Factory)입니다.

  1. 단순 팩토리(Simple Factory): 단순 팩토리는 클라이언트가 필요로 하는 객체를 생성하는 역할을 합니다. 클라이언트는 생성할 객체의 타입을 지정하고, 단순 팩토리는 해당 타입의 객체를 생성하여 반환합니다.
  2. 팩토리 메서드(Factory Method): 팩토리 메서드 패턴은 팩토리를 인터페이스 혹은 추상 클래스로 정의하고, 이를 구현한 각각의 구체적인 팩토리 클래스가 실제 객체 생성을 담당하도록 합니다. 클라이언트는 구체적인 객체 타입을 몰라도 되며, 팩토리 인터페이스를 통해 객체를 생성합니다. 이렇게 함으로써 클라이언트와 객체 생성 코드 사이의 결합도를 낮추는 것이 목표입니다.
  3. 추상 팩토리(Abstract Factory): 추상 팩토리 패턴은 서로 연관된, 또는 의존적인 객체들의 그룹을 생성할 수 있는 인터페이스를 제공합니다. 각각의 팩토리는 독립적인 프로덕트를 생성하지만, 모든 프로덕트는 공통의 테마를 공유합니다.

팩토리 패턴은 다음과 같은 이점을 제공합니다:

  • 객체 생성과 사용을 분리하여 코드의 결합도를 낮춥니다.
  • 단일 책임 원칙(Single Responsibility Principle)을 따르며, 객체 생성 코드를 한 곳에 모아 관리합니다.
  • 코드의 유연성과 재사용성을 높입니다. 새로운 객체 타입을 추가하더라도 클라이언트 코드를 변경할 필요가 없습니다.
  • 테스트하기 좋은 코드를 작성하는 도움을 줍니다. 팩토리를 통해 생성된 객체는 대체 가능하므로, 테스트 실제 객체 대신 모의 객체(mock object) 사용할 있습니다.

단순 팩토리 패턴, 팩토리 메서드 패턴, 추상 팩토리 패턴은 모두 객체 생성에 관련된 디자인 패턴입니다. 이 세 가지 패턴을 쉽게 이해하기 위해 간단한 예제를 제공하겠습니다.

 

단순 팩토리 패턴 (Simple Factory Pattern)

public interface Animal {
    void speak();
}

public class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("Bark");
    }
}

public class Cat implements Animal {
    @Override
    public void speak() {
        System.out.println("Meow");
    }
}

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if (type.equalsIgnoreCase("dog")) {
            return new Dog();
        } else if (type.equalsIgnoreCase("cat")) {
            return new Cat();
        }
        return null;
    }
}

 

 

팩토리 메서드 패턴 (Factory Method Pattern)

public interface AnimalFactory {
    Animal createAnimal();
}

public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}

public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}

 

 

추상 팩토리 패턴 (Abstract Factory Pattern)

 

public interface Food {
    void eat();
}

public class DogFood implements Food {
    @Override
    public void eat() {
        System.out.println("Dog food");
    }
}

public class CatFood implements Food {
    @Override
    public void eat() {
        System.out.println("Cat food");
    }
}

public interface AnimalFactory {
    Animal createAnimal();
    Food createFood();
}

public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }

    @Override
    public Food createFood() {
        return new DogFood();
    }
}

public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }

    @Override
    public Food createFood() {
        return new CatFood();
    }
}

 

위의 예제들에서 보시다시피, 팩토리 패턴은 객체 생성 로직을 클라이언트 코드에서 분리하고, 대신 팩토리 클래스를 사용해 객체를 생성하는 것입니다. 이는 코드의 결합도를 낮추고, 확장성과 유지 관리를 향상시킵니다.

 

단순 팩토리 특징

 

단순 팩토리 패턴(Simple Factory Pattern)은 디자인 패턴 중 하나입니다. 이 패턴의 주요 특징은 다음과 같습니다.

 

1. 객체 생성 캡슐화: 단순 팩토리 패턴의 주요 목적은 객체 생성 로직을 캡슐화하는 것입니다. 이렇게 하면 클라이언트 코드가 특정 객체의 구체적인 클래스를 직접 참조할 필요가 없습니다.

 

2. 코드의 간결성: 클라이언트 코드에서는 팩토리 클래스의 메소드를 호출하여 필요한 객체를 생성합니다. 따라서 클라이언트 코드는 객체 생성 과정에 대해 알 필요가 없으며, 이는 코드를 더욱 간결하고 명확하게 만듭니다.

 

3. 유지 관리의 용이성: 객체 생성 로직이 한 곳(즉, 팩토리 클래스)에 집중되므로, 필요한 경우 이 로직을 쉽게 수정하거나 확장할 수 있습니다.

 

4. 생성 로직의 재사용성: 팩토리 클래스의 메소드는 어디서든 재사용될 수 있으므로, 동일한 객체 생성 로직을 반복적으로 사용해야 하는 경우에 이 패턴은 매우 유용합니다.

 

5. 타입 안정성: 팩토리 메소드가 항상 일정한 타입의 객체를 반환하기 때문에, 클라이언트는 반환된 객체의 타입을 확인하지 않고도 안전하게 사용할 수 있습니다.

 

하지만 단순 팩토리 패턴에는 가지 단점도 있습니다. 예를 들어, 팩토리 클래스가 생성할 있는 객체의 종류가 늘어날수록 팩토리 메소드 내의 분기문이 복잡해질 있습니다. 이런 경우 팩토리 메서드 패턴이나 추상 팩토리 패턴을 고려해볼 있습니다.

 

팩토리 메서드 패턴 (Factory Method Pattern)

 

팩토리 메서드 패턴에서는 객체 생성 로직을 서브 클래스에 위임합니다. 즉, 인터페이스를 정의하고 어떤 클래스가 이 인터페이스를 구현할지는 서브 클래스에서 결정하도록 합니다. 이는 상위 클래스에 의존하지 않고 객체를 생성할 수 있도록 합니다. 이 패턴은 주로 다음과 같은 경우에 사용됩니다:

 

- 클래스는 생성할 객체의 클래스를 예측할 수 없을 때

- 클래스가 자신이 생성해야 하는 객체의 클래스를 서브 클래스에 위임하려고 할 때

- 클래스는 자식 클래스가 확장할 있는 (hook) 제공하려고

 

추상 팩토리 패턴 (Abstract Factory Pattern)

 

추상 팩토리 패턴에서는 인터페이스를 통해 관련성이 있는 또는 의존성이 있는 객체들을 생성할 수 있도록 합니다. 이 패턴은 인터페이스만을 사용하여 객체를 생성하고, 실제 생성은 해당 인터페이스를 구현한 클래스가 담당합니다. 이 패턴은 주로 다음과 같은 경우에 사용됩니다:

 

- 시스템은 독립적인 플랫폼에 투명하게 동작해야 할 때

- 시스템은 서로 관련되거나 의존적인 여러 객체를 구성하려고 할 때

- 객체 집합에 대해 특정 행동을 지정하려고 할 때

 

패턴 모두 인터페이스를 통해 객체 생성을 추상화하고, 구체적인 생성은 해당 인터페이스를 구현한 클래스에 위임하는 것이 공통점입니다. 하지만 팩토리 메서드 패턴은 하나의 메서드에 초점을 맞추고 있으며, 추상 팩토리 패턴은 "제품 가족" 생성하는 것에 초점을 맞추고 있습니다. 둘을 적절하게 조합하면 유연하고 강력한 설계를 있습니다.

 

팩토리 패턴 사용 이유

팩토리 디자인 패턴은 객체 생성에 관련된 로직을 감추는 데 유용한 방법입니다. 이를 사용하는 이유는 다음과 같습니다:

1. 객체 생성의 분리: 팩토리 패턴을 사용하면 객체 생성과 그것을 사용하는 코드를 분리할 수 있습니다. 이는 코드의 결합도를 줄이고, 유지보수와 코드 재사용을 용이하게 합니다.

2. 단일 책임 원칙: 객체 생성에 관련된 로직이 복잡한 경우, 이 로직을 분리하고 단일 책임 원칙(Single Responsibility Principle)을 따르게 하는데 도움이 됩니다.

3. 확장성: 팩토리 패턴은 새로운 객체 타입이 추가되더라도 기존 코드를 수정하지 않고도 해당 객체를 생성할 수 있습니다. 이는 Open-Closed Principle (개방-폐쇄 원칙)을 따르는 것을 돕습니다.

4. 재사용 및 테스트 용이성: 팩토리 패턴은 같은 타입의 객체를 반복해서 생성할 때 유용합니다. 또한, 팩토리를 통해 객체를 생성하면 단위 테스트가 용이해집니다.

5. 의존성 관리: 의존성 주입(Dependency Injection)이 필요한 경우 팩토리 패턴을 사용하면 객체 생성과 의존성 관리를 효과적으로 할 수 있습니다.

이러한 이유로, 팩토리 디자인 패턴은 객체 지향 프로그래밍에서 자주 사용되는 중요한 패턴 중 하나입니다.

 

팩토리 패턴 사용 시 주의 해야 할 점

팩토리 패턴은 많은 장점을 제공하지만, 잘못 사용하면 시스템에 문제를 일으킬 수 있습니다. 다음은 팩토리 패턴을 사용할 때 주의해야 할 점들입니다:

1. 오버 엔지니어링: 객체 생성이 복잡하지 않은데도 팩토리 패턴을 사용하는 것은 오버 엔지니어링을 초래할 수 있습니다. 꼭 필요한 경우에만 팩토리 패턴을 사용하도록 주의해야 합니다.

2. 유지보수성: 팩토리 패턴은 새로운 타입의 객체를 생성하려면 팩토리 클래스 자체를 수정해야 하는 경우가 많습니다. 이는 유지보수성을 저해할 수 있으니 적절히 사용해야 합니다.

3. 복잡성: 팩토리 패턴은 객체를 생성하는 로직을 추상화하여 복잡성을 증가시킬 수 있습니다. 이로 인해 코드를 이해하고 디버깅하는데 어려움이 생길 수 있습니다.

4. 테스트 문제: 팩토리 메서드가 복잡한 로직을 포함하면 테스트하기 어려워질 수 있습니다. 이를 완화하기 위해 팩토리 메서드의 로직을 가능한 간단하게 유지하거나, 적절한 mocking 기법을 사용해야 합니다.

따라서 팩토리 패턴은 그 필요성과 가치를 충분히 검토한 후, 적절한 경우에만 사용해야 합니다.