디자인 패턴은 소프트웨어 개발에서 반복적으로 발생하는 문제에 대한 재사용 가능한 솔루션입니다. 문제 해결 범위에 따라 생성(Creational), 구조(Structural), 행동(Behavioral) 패턴으로 분류됩니다.
본 문서에서는 객체 생성 과정을 추상화하여 시스템의 유연성과 확장성을 높이는 5가지 생성 패턴(싱글턴, 팩토리 메서드, 추상 팩토리, 빌더, 프로토타입)을 심층 분석합니다.
생성 패턴 개요
| 패턴 | 중요도 | 핵심 목적 | 주요 특징 | C++ 모범 사례 |
|---|---|---|---|---|
| 싱글턴 | 5 | 클래스의 인스턴스가 오직 하나임을 보장하고 전역 접근점 제공 | 전역 유일 인스턴스 | std::call_once와 std::once_flag로 스레드 안전성 확보; std::shared_ptr로 생명주기 관리 |
| 팩토리 메서드 | 5 | 객체 생성을 위한 인터페이스 정의, 서브클래스에서 구체 클래스 결정 | 생성과 사용의 분리, 개방-폐쇄 원칙 준수 | 다형성을 위해 팩토리 클래스에 가상 함수 사용; 조건문 지양 |
| 추상 팩토리 | 4 | 관련 객체군을 생성하기 위한 인터페이스 제공 | 연관된 객체군 생성, 통합된 인터페이스 | std::shared_ptr 반환; 조건문 없는 객체 생성 로직 구현 |
| 빌더 | 4 | 복잡한 객체의 구성과 표현을 분리하여 동일한 과정으로 다른 표현 생성 | 단계별 객체 생성, 생성자 인수 폭발 방지 | 메서드 체이닝(Fluent Interface) 활용; 빌더 내 복잡한 상태 저장 자제 |
| 프로토타입 | 3 | 기존 객체를 복제하여 새로운 객체 생성 | 복제를 통한 효율적 객체 생성 | clone() 메서드 구현; 깊은 복사 시 메모리 관리 주의 |
1. 싱글턴 패턴 (Singleton) - 중요도 5
핵심: 클래스의 인스턴스가 오직 하나만 존재하도록 보장하고, 이에 대한 전역 접근점을 제공합니다.
장점: 유일한 인스턴스에 대한 전역 접근점 제공, 자원 낭비 방지, 인스턴스 생성 과정 제어 가능.
단점: 전역 상태로 인한 결합도 증가, 테스트 어려움, 부적절한 관리 시 메모리 누수 가능성.
// LoggerSingleton.h
#pragma once
#include <memory>
#include <mutex>
#include <iostream>
class Logger {
private:
Logger() = default; // 외부 생성 차단
static std::shared_ptr<Logger> instance;
static std::once_flag initFlag;
public:
static std::shared_ptr<Logger> getInstance() {
std::call_once(initFlag, [] {
instance = std::make_shared<Logger>();
});
return instance;
}
// 복사 및 이동 금지
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
void log(const std::string& message) {
std::cout << "[Logger] " << message << std::endl;
}
~Logger() { std::cout << "Logger destroyed." << std::endl; }
};
// 정적 멤버 초기화
std::shared_ptr<Logger> Logger::instance = nullptr;
std::once_flag Logger::initFlag;
// main.cpp
#include "LoggerSingleton.h"
int main() {
auto logger1 = Logger::getInstance();
auto logger2 = Logger::getInstance();
if (logger1 == logger2) {
logger1->log("두 인스턴스는 동일한 객체입니다.");
}
logger2->log("애플리케이션 시작됨");
return 0;
}
2. 팩토리 메서드 패턴 (Factory Method) - 중요도 5
핵심: 객체 생성을 위한 인터페이스를 정의하고, 서브클래스가 어떤 클래스를 인스턴스화할지 결정하게 합니다.
// Document.h
#pragma once
#include <memory>
#include <iostream>
class Document {
public:
virtual ~Document() = default;
virtual void open() = 0;
};
class TextDocument : public Document {
public:
void open() override { std::cout << "텍스트 문서 열기" << std::endl; }
};
class ImageDocument : public Document {
public:
void open() override { std::cout << "이미지 문서 열기" << std::endl; }
};
class Application {
public:
virtual ~Application() = default;
virtual std::shared_ptr<Document> createDocument() = 0; // 팩토리 메서드
void newDocument() {
auto doc = createDocument();
docs_.push_back(doc);
doc->open();
}
private:
std::vector<std::shared_ptr<Document>> docs_;
};
class TextApplication : public Application {
public:
std::shared_ptr<Document> createDocument() override {
return std::make_shared<TextDocument>();
}
};
class ImageApplication : public Application {
public:
std::shared_ptr<Document> createDocument() override {
return std::make_shared<ImageDocument>();
}
};
3. 추상 팩토리 패턴 (Abstract Factory) - 중요도 4
핵심: 서로 연관되거나 의존적인 객체 군을 생성하기 위한 인터페이스를 제공하며, 구체적인 클래스를 지정하지 않습니다.
// GUIFactory.h
class Button {
public:
virtual ~Button() = default;
virtual void render() = 0;
};
class Checkbox {
public:
virtual ~Checkbox() = default;
virtual void render() = 0;
};
class WindowsButton : public Button {
public:
void render() override { std::cout << "Windows 버튼 렌더링" << std::endl; }
};
class WindowsCheckbox : public Checkbox {
public:
void render() override { std::cout << "Windows 체크박스 렌더링" << std::endl; }
};
class MacOSButton : public Button {
public:
void render() override { std::cout << "MacOS 버튼 렌더링" << std::endl; }
};
class MacOSCheckbox : public Checkbox {
public:
void render() override { std::cout << "MacOS 체크박스 렌더링" << std::endl; }
};
class GUIFactory {
public:
virtual ~GUIFactory() = default;
virtual std::shared_ptr<Button> createButton() = 0;
virtual std::shared_ptr<Checkbox> createCheckbox() = 0;
};
class WindowsFactory : public GUIFactory {
public:
std::shared_ptr<Button> createButton() override {
return std::make_shared<WindowsButton>();
}
std::shared_ptr<Checkbox> createCheckbox() override {
return std::make_shared<WindowsCheckbox>();
}
};
class MacOSFactory : public GUIFactory {
public:
std::shared_ptr<Button> createButton() override {
return std::make_shared<MacOSButton>();
}
std::shared_ptr<Checkbox> createCheckbox() override {
return std::make_shared<MacOSCheckbox>();
}
};
4. 빌더 패턴 (Builder) - 중요도 4
핵심: 복잡한 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차가 다른 표현 결과를 만들 수 있게 합니다.
// Computer.h
#include <string>
#include <iostream>
class Computer {
public:
void setCPU(const std::string& cpu) { cpu_ = cpu; }
void setRAM(const std::string& ram) { ram_ = ram; }
void setStorage(const std::string& storage) { storage_ = storage; }
void showSpecs() const {
std::cout << "CPU: " << cpu_ << ", RAM: " << ram_
<< ", Storage: " << storage_ << std::endl;
}
private:
std::string cpu_;
std::string ram_;
std::string storage_;
};
class ComputerBuilder {
public:
virtual ~ComputerBuilder() = default;
virtual void buildCPU() = 0;
virtual void buildRAM() = 0;
virtual void buildStorage() = 0;
virtual Computer getResult() = 0;
};
class GamingComputerBuilder : public ComputerBuilder {
public:
void buildCPU() override { computer_.setCPU("Intel i9"); }
void buildRAM() override { computer_.setRAM("32GB DDR5"); }
void buildStorage() override { computer_.setStorage("1TB NVMe SSD"); }
Computer getResult() override { return computer_; }
private:
Computer computer_;
};
class OfficeComputerBuilder : public ComputerBuilder {
public:
void buildCPU() override { computer_.setCPU("Intel i5"); }
void buildRAM() override { computer_.setRAM("16GB DDR4"); }
void buildStorage() override { computer_.setStorage("512GB SSD"); }
Computer getResult() override { return computer_; }
private:
Computer computer_;
};
class Director {
public:
void setBuilder(ComputerBuilder* builder) { builder_ = builder; }
Computer construct() {
builder_->buildCPU();
builder_->buildRAM();
builder_->buildStorage();
return builder_->getResult();
}
private:
ComputerBuilder* builder_;
};
5. 프로토타입 패턴 (Prototype) - 중요도 3
핵심: 기존 객체를 복제(Clone)하여 새로운 객체를 생성합니다.
// Shape.h
#pragma once
#include <memory>
#include <iostream>
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<Shape> clone() const = 0;
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
Circle(int radius) : radius_(radius) {}
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Circle>(*this);
}
void draw() const override {
std::cout << "원 그리기 (반지름: " << radius_ << ")" << std::endl;
}
void setRadius(int r) { radius_ = r; }
private:
int radius_;
};
class Rectangle : public Shape {
public:
Rectangle(int w, int h) : width_(w), height_(h) {}
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Rectangle>(*this);
}
void draw() const override {
std::cout << "사각형 그리기 (폭: " << width_ << ", 높이: " << height_ << ")" << std::endl;
}
private:
int width_;
int height_;
};
int main() {
auto circlePrototype = std::make_shared<Circle>(10);
auto clonedCircle = circlePrototype->clone();
if (circlePrototype.get() != clonedCircle.get()) {
std::cout << "복제된 객체는 원본과 다릅니다." << std::endl;
}
clonedCircle->draw(); // 원 그리기 (반지름: 10)
return 0;
}