생성 패턴 완전 정복: 싱글턴, 팩토리, 빌더, 프로토타입

디자인 패턴은 소프트웨어 개발에서 반복적으로 발생하는 문제에 대한 재사용 가능한 솔루션입니다. 문제 해결 범위에 따라 생성(Creational), 구조(Structural), 행동(Behavioral) 패턴으로 분류됩니다.

본 문서에서는 객체 생성 과정을 추상화하여 시스템의 유연성과 확장성을 높이는 5가지 생성 패턴(싱글턴, 팩토리 메서드, 추상 팩토리, 빌더, 프로토타입)을 심층 분석합니다.

생성 패턴 개요

패턴 중요도 핵심 목적 주요 특징 C++ 모범 사례
싱글턴 5 클래스의 인스턴스가 오직 하나임을 보장하고 전역 접근점 제공 전역 유일 인스턴스 std::call_oncestd::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;
}

태그: singleton Factory Method Abstract Factory Builder Prototype

6월 8일 04:00에 게시됨