반응형

Factory Method 패턴은 생성 패턴의 한 종류로, 객체 생성에 대한 책임을 서브클래스로 위임하여, 객체 생성 방식을 캡슐화하고 클라이언트가 객체 생성 방식에 독립적으로 동작하도록 합니다. 객체 생성이 필요한 경우 이 패턴을 사용하여 클라이언트 코드의 유연성과 확장성을 높일 수 있습니다. 다음은 C++를 기준으로 Factory Method 패턴의 구조와 코드 예제를 포함하여 자세히 설명합니다.

Factory Method 패턴의 구조

  1. Product (제품 인터페이스): 생성될 객체들이 구현해야 할 인터페이스 또는 추상 클래스입니다.
  2. ConcreteProduct (구체적 제품): Product 인터페이스를 구현하는 실제 클래스들로, 다양한 형태의 Product 객체가 됩니다.
  3. Creator (생성자 인터페이스): 팩토리 메서드 (FactoryMethod())를 정의하는 인터페이스로, 객체 생성 로직을 서브클래스에서 구현하도록 위임합니다.
  4. ConcreteCreator (구체적 생성자): Creator 클래스를 상속하여, 특정 구체적 제품 객체를 생성하는 팩토리 메서드를 구현한 클래스입니다.

동작 과정

  1. 클라이언트 코드Creator의 인스턴스에게 FactoryMethod를 호출하여 Product 타입의 객체 생성을 요청합니다.
  2. ConcreteCreator는 FactoryMethod를 통해 ConcreteProduct의 객체를 생성하고, 이를 Product 타입으로 반환합니다.
  3. 클라이언트는 Product 인터페이스만을 사용하여 객체와 상호작용합니다. 구체적으로 어떤 객체가 생성되었는지 알 필요가 없습니다.

C++ 코드 예제

1. Product 인터페이스와 구체적인 제품 클래스들

 

  • Product는 객체 생성 결과로 얻을 수 있는 공통 인터페이스입니다. 각 제품 클래스는 Operation이라는 순수 가상 함수를 구현해야 합니다.
  • ConcreteProductAConcreteProductB는 Product를 상속받아 구체적인 제품 기능을 제공합니다

 

2. Creator 클래스와 구체적인 생성자 클래스들

  • Creator는 FactoryMethod()라는 순수 가상 함수를 통해 제품 객체를 생성하는 인터페이스입니다. SomeOperation()이라는 함수는 FactoryMethod()를 호출하여 생성된 제품 객체와 작업을 수행합니다.
  • ConcreteCreatorAConcreteCreatorB는 Creator를 상속하며, 각기 다른 ConcreteProduct 객체를 생성하여 반환하는 팩토리 메서드를 구현합니다.

3. 클라이언트 코드

클라이언트는 Creator 인터페이스를 통해 제품을 생성하므로, 실제로 어떤 ConcreteProduct가 반환되는지 알 필요가 없습니다.

이 예제에서는 ClientCode 함수가 Creator 객체를 통해 작업을 수행합니다. SomeOperation() 메서드가 제품 생성 과정을 감추므로, 클라이언트는 구체적인 생성자 클래스가 어떤 제품을 생성하는지 알 필요가 없습니다.

출력 결과

이 코드를 실행하면 다음과 같은 출력이 나옵니다:

패턴의 장점과 단점

장점

  1. 유연성: 클라이언트가 객체 생성에 구체적으로 관여하지 않아, 새로운 제품을 추가할 때 기존 클라이언트 코드를 수정할 필요가 없습니다.
  2. 객체 생성 캡슐화: 객체 생성 로직이 숨겨져 있어 코드가 더 간결하고 유지보수하기 쉽습니다.
  3. 확장성: 새로운 제품을 추가할 때 ConcreteCreator와 ConcreteProduct만 추가하면 되므로 코드의 확장성이 높습니다.

단점

  1. 클래스 수 증가: 새로운 ConcreteProduct와 이를 생성하는 ConcreteCreator가 필요하기 때문에 클래스 수가 늘어나 복잡도가 높아질 수 있습니다.
  2. 설계가 복잡해질 가능성: 팩토리 메서드 패턴을 사용하지 않아도 충분한 경우에도 과도하게 적용하면 코드가 불필요하게 복잡해질 수 있습니다.

활용 사례

  • 다양한 객체 생성이 필요한 상황: 예를 들어, 그래픽 프로그램에서 다양한 도형(원, 사각형, 삼각형 등)을 생성해야 하는 경우 각 도형별로 ConcreteProduct와 ConcreteCreator를 두면 코드 확장성이 높아집니다.
  • 객체 생성 로직이 복잡하거나 특정 조건에 따라 다른 객체를 생성해야 하는 경우: 다양한 요구 사항에 따라 객체 생성 방식이 달라질 때 유용합니다.

이로써 Factory Method 패턴을 C++로 구현하여 클라이언트와 객체 생성 로직을 분리하는 방법을 이해할 수 있습니다.

 

반응형

C++에서 싱글턴(Singleton) 패턴을 구현할 때 고려해야 할 부분들이 많습니다. 특히, 멀티스레드 환경과 메모리 관리, 파괴 시점에 대한 관리 등 여러 가지를 신중히 설계해야 합니다. 보다 자세히 설명하겠습니다.

싱글턴 패턴의 구조

싱글턴 클래스는 다음과 같은 요소들로 구성됩니다:

  1. 정적 인스턴스 포인터: 클래스 내의 static 포인터 변수로 싱글턴 인스턴스를 가리킵니다. 이는 해당 클래스가 단 하나의 객체만을 생성하는 것을 보장하기 위한 핵심 요소입니다.
  2. private 생성자: 생성자를 private으로 선언하여 외부에서 직접 객체를 생성하지 못하게 막습니다.
  3. public 정적 메서드 (getInstance): 싱글턴 객체에 접근하는 유일한 방법으로, 객체가 생성되지 않았을 때만 인스턴스를 생성하고 반환합니다. 이를 통해 전역적으로 접근 가능한 단일 인스턴스를 보장합니다.
  4. 복사 방지: 복사 생성자와 대입 연산자를 delete 처리하여 객체의 복사와 대입을 금지합니다. 이를 통해 중복 인스턴스 생성을 방지할 수 있습니다.
  5. 멀티스레드 안전성: 멀티스레드 환경에서도 싱글턴 인스턴스가 한 번만 생성되도록 mutex와 같은 락을 사용해 동기화합니다.

구체적인 구현 예제

1. 정적 포인터와 Mutex 초기화

싱글턴 클래스의 핵심은 전역적으로 접근할 수 있는 유일한 인스턴스를 갖는 것이므로, 정적 포인터 변수를 통해 객체를 관리합니다.

위 코드에서 getInstance()는 처음 호출될 때 instance가 nullptr인지 확인합니다. nullptr인 경우에만 뮤텍스를 잠근 상태로 인스턴스를 생성합니다. 두 번째 검사(if (instance == nullptr))는 여러 스레드에서 동시에 접근해 인스턴스가 두 번 생성되는 것을 방지하기 위함입니다.

 

2. 메모리 관리 (객체 해제)

싱글턴 패턴을 구현할 때는 메모리 관리에도 주의해야 합니다. C++에서 객체를 할당하고 명시적으로 해제하지 않으면 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 정적 멤버 함수를 통해 프로그램 종료 시 인스턴스를 해제하는 방법을 사용할 수 있습니다.

C++11 이후 std::unique_ptr을 이용한 안전한 메모리 관리

C++11 이후에서는 std::unique_ptr을 사용하여 소멸자를 자동 호출해 메모리 관리를 할 수 있습니다.

위 예제에서는 std::unique_ptr을 통해 인스턴스를 자동으로 해제하므로 메모리 누수를 방지할 수 있습니다.

 

3. C++11의 call_once와 once_flag를 이용한 구현

C++11부터는 std::call_once와 std::once_flag를 사용하여 더 간단하고 안전하게 싱글턴을 생성할 수 있습니다.

std::call_once와 std::once_flag를 이용하면 mutex를 직접 사용하지 않고도 인스턴스를 안전하게 생성할 수 있습니다. std::call_once는 getInstance()가 호출될 때 instance가 nullptr일 때만 한 번 실행되므로 멀티스레드 환경에서의 중복 생성 문제를 효과적으로 해결합니다.

 

싱글턴 패턴의 장단점 정리

장점

  • 일관된 접근: 애플리케이션 내에서 단 하나의 인스턴스를 관리하므로 공통된 자원이나 상태를 안전하게 관리할 수 있습니다.
  • 자원 절약: 인스턴스가 하나만 존재하므로 불필요한 메모리 할당을 줄일 수 있습니다.
  • 전역 접근성: 인스턴스를 전역적으로 접근할 수 있어 코드 전반에서 유용하게 사용할 수 있습니다.

단점

  • 의존성 증가: 싱글턴 패턴은 전역 접근을 허용하여 코드의 의존성이 높아지고, 테스트 시 의존 객체를 주입하거나 분리하기 어려울 수 있습니다.
  • 메모리 관리: 명시적으로 객체를 소멸시키지 않으면 프로그램 종료 시까지 인스턴스가 남아 메모리 누수가 발생할 수 있습니다.
  • 멀티스레드 환경의 복잡성: 멀티스레드 환경에서는 잠금 관리나 동기화 처리가 필요해 복잡해질 수 있습니다.

이러한 요소들을 고려하여 필요한 경우에만 싱글턴 패턴을 사용하는 것이 좋습니다.

+ Recent posts