ABOUT ME

-

  • 디자인 패턴 정리 - 01. 생성 패턴
    프로그래밍 지식/디자인 패턴 2024. 6. 3. 22:50

    생성 패턴

    • Simple Factory 패턴
      : 객체를 생성하는 팩토리를 따로 두는 것
      , 객체 생성 로직을 클라이언트 코드에서 분리

    • 장점 : 복잡한 오브젝트의 생성 과정을 클라이언트가 직접 다룰 필요 없음
      • 어떤 상황에서는 Object를 생성하는 과정이 복잡할 수 있다.
    • 오브젝트 생성 과정은 모두 팩토리 안에 숨겨놓고 클라이언트는 "테슬라 모델3 만들어주세요" "테슬라 모델Y 만들어주세요"를 요청하면 팩토리가 필요한 오브젝트를 생성하여 return
    #include <iostream>
    #include <string>
    #include <memory>
    
    class Car {
    public:
        virtual ~Car() = default;
        virtual void brake() = 0;
    };
    
    class Model3 : public Car {
    public:
        void brake() override {
            std::cout << "Modle3 brake" << std::endl;
        }
    };
    
    class ModelY : public Car {
    public:
        void brake() override {
            std::cout << "ModelY brake" << std::endl;
        }
    };
    
    // Factory 함수에서 객체를 생성
    class TeslaFactory {
    public:
        std::unique_ptr<Car> createCar(const std::string& type) const {
            if (type == "Model3") {
                return std::make_unique<Model3>();
            }
            else if(type == "ModelY") {
                return std::make_unique<ModelY>();
            }
            else
            {
                return nullptr;
            }
        }
    };
    
    // Client 함수
    int main() {
        TeslaFactory teslaFactory;
        auto car1 = teslaFactory.createCar("Model3");
        if (car1) {
            car1->brake();
        }
        auto car2 = teslaFactory.createCar("ModelY");
        if (car2) {
            car2->brake();
        }
    }
    • Factory Method Pattern
      • 객체 생성 메서드를 하위 클래스에 위임하여 객체 생성을 캡슐화하는 패턴
      • 객체 생성의 확장을 용이하게 함
      • 단일 객체 생성

    #include <iostream>
    #include <string>
    #include <memory>
    
    class Car {
    public:
        virtual ~Car() = default;
        virtual void brake() = 0;
    };
    
    class Model3 : public Car {
    public:
        void brake() override {
            std::cout << "Modle3 brake" << std::endl;
        }
    };
    
    class ModelY : public Car {
    public:
        void brake() override {
            std::cout << "ModelY brake" << std::endl;
        }
    };
    
    class TeslaFactory {
    public:
        virtual ~TeslaFactory() {}
        // 객체 생성을 위한 메서드 (단일)
        virtual std::unique_ptr<Car> createCar() const = 0;
    
        void brake() const {
            auto car = createCar();
            car->brake();
        }
    };
    
    class Model3Factory : public TeslaFactory {
    public:
        std::unique_ptr<Car> createCar() const override {
            return std::make_unique<Model3>();
        }
        //costom 가능
    };
    
    class ModelYFactory : public TeslaFactory {
    public:
        std::unique_ptr<Car> createCar() const override {
            return std::make_unique<ModelY>();
        }
        //costom 가능
    };
    
    // Client 함수
    int main() {
        Model3Factory model3Factory;
        auto car1 = model3Factory.createCar();
        if (car1) {
            car1->brake();
        }
        ModelYFactory modelYFactory;
        auto car2 = modelYFactory.createCar();
        if (car2) {
            car2->brake();
        }
    }
    • 심플 팩토리 패턴 vs 팩토리 메서드 패턴
      • 객체 생성 책임
        • 심플 팩토리 패턴 : 클라이언트가 팩토리 클래스를 사용하여 객체 생성
        • 팩토리 메서드 패턴 : 객체 생성 메서드가 추상 클래스나 인터페이스에 정의되고 하위 클래스에서 구현. 클라이언트가 하위 클래스를 사용하여 객체 생성
      • 확장성
        • 심플 팩토리 패턴 : 새로운 타입의 객체를 생성하려면 팩토리 클래스 코드 수정 필요
        • 팩토리 메서드 패턴 : 새로운 타입의 객체를 생성하려면 새로운 하위 클래스를 추가. 기존 클래스 수정 필요 X
      • 사용 시기
        • 심플 팩토리 패턴 : 객체 생성 로직이 자주 변경되지 않거나 비교적 단순할 때
        • 팩토리 메서드 패턴 : 객체 생성 로직이 자주 변경되거나 확장될 가능성이 있을 때
    • Abstract Factory Pattern
      • 관련된 객체의 집합을 생성하는 인터페이스를 제공
      • 다수 객체 생성

    #include <iostream>
    #include <string>
    #include <memory>
    
    class Brake {
    public:
        virtual ~Brake() = default;
        virtual void use() = 0;
    };
    
    class Window {
    public:
        virtual ~Window() = default;
        virtual void use() = 0;
    };
    
    class Model3Brake : public Brake {
    public:
        void use() override {
            std::cout << "Modle3 brake" << std::endl;
        }
    };
    
    class ModelYBrake : public Brake {
    public:
        void use() override {
            std::cout << "ModelY brake" << std::endl;
        }
    };
    
    class Model3Window : public Window {
    public:
        void use() override {
            std::cout << "Modle3 window" << std::endl;
        }
    };
    
    class ModelYWindow : public Window {
    public:
        void use() override {
            std::cout << "ModelY window" << std::endl;
        }
    };
    
    class TeslaFactory {
    public:
        virtual ~TeslaFactory() {}
        virtual std::unique_ptr<Brake> createBrkae() const = 0;
        virtual std::unique_ptr<Window> createWindow() const = 0;
    };
    
    class Model3Factory : public TeslaFactory {
    public:
        // 복수의 관련 객체들 집합 생성
        virtual std::unique_ptr<Brake> createBrkae() const override {
            return std::make_unique<Model3Brake>();
        }
        virtual std::unique_ptr<Window> createWindow() const override {
            return std::make_unique<Model3Window>();
        }
        //costom 가능
    };
    
    class ModelYFactory : public TeslaFactory {
    public:
        virtual std::unique_ptr<Brake> createBrkae() const override {
            return std::make_unique<ModelYBrake>();
        }
        virtual std::unique_ptr<Window> createWindow() const override {
            return std::make_unique<ModelYWindow>();
        }
        //costom 가능
    };
    
    // Client 함수
    int main() {
        Model3Factory model3Factory;
        auto brake1 = model3Factory.createBrkae();
        auto window1 = model3Factory.createWindow();
        brake1->use();
        window1->use();
    
        ModelYFactory modelYFactory;
        auto brake2 = modelYFactory.createBrkae();
        auto window2 = modelYFactory.createWindow();
        brake2->use();
        window2->use();
    
    }

    팩토리 메서드 패턴 vs 추상 팩토리 패턴

    • 목적 :
      • 팩토리 메서드 패턴 : 단일 객체의 생성
      • 추상 팩토리 패턴 : 관련된 객체들의 집합을 생성
    • 구조 : 
      • 팩토리 메서드 패턴 : 객체 생성을 위한 하나의 메서드가 있으며, 구체적인 서브 클래스가 이 메서드를 구현하여 객체 생성
      • 추상 팩토리 패턴 : 여러 종류의 객체를 생성하는 여러 팩토리 메서드를 가진 인터페이스가 있으며, 구체적인 팩토리 클래스가 이 인터페이스 관련된 객체들을 생성
    • 확장성 :
      • 팩토리 메서드 패턴 : 새로운 객체 유형을 추가할 때 새로운 서브클래스 추가하여 팩토리 메서드 오버라이드
      • 추상 팩토리 패턴 : 새로운 제품군을 추가할 때 새로운 팩토리 클래스를 추가하여 관련된 모든 제품을 생성하는 메서드 구현

    ex. 제품군 객체들을 모두 만들어야할 때

    • 팩토리 메서드 일 때 Client : 모델3 브레이크 객체 만들어주세요, 모델3 윈도우 객체 만들어주세요, ...
    • 추상 팩토리 일 때 Client : 모델3와 관련된 객체들 만들어주세요 (팩토리 메서드를 한번 더 추상화)

     

    팩토리 관련 패턴들의 차이점을 이해하기 어려웠다. (모두 추상화를 사용하고 코드 상 비슷해보였기 때문이다.)

    구현의 차이점을 명확히 아는 것 보다는 각각의 패턴을 사용하는 컨셉이 중요한 것 같다. 내가 이해한 바로는 하기와 같다.

    • 심플 팩토리 패턴 : 객체 생성하는 과정은 안 보여줄게. 클라이언트는 그냥 뭐 사용하고싶은지만 말해
    • 팩토리 메서드 팩턴 : 객체 생성을 상위 클래스와 하위 클래스로 나눠서 나중에 다른 종류의 하위 클래스가 생길 확장성도 고려할래
    • 추상 팩토리 패턴 : 클라이언트가 뭐 사용하고 싶다고 말하면 관련된 제품군들 객체를 싹 다 만들어줄래

    Builder Pattern

    #include <iostream>
    #include <string>
    
    class Car {
    public:
        void setWindow(const std::string &window) {
            this->window = window;
        }
        void setWheels(const std::string &wheels) {
            this->wheels = wheels;
        }
        void setBrake(const std::string &brake) {
            this->brake = brake;
        }
        void setSeats(const int seats) {
            this->seats = seats;
        }
        void show() const {
            std::cout << "Car with window: " << window
            << ", wheels: " << wheels
            << ", seats: " << seats
            << ", brake: " << brake << std::endl;
        }
    private:
        std::string window;
        std::string wheels;
        std::string brake;
        int seats;
    };
    
    class CarBuilder {
    public:
        virtual ~CarBuilder() = default;
    
        Car* getCar() {
            return car;
        }
    
        void createNewCar() {
            car = new Car();
        }
    
        virtual void buildWindow() = 0;
        virtual void buildWheels() = 0;
        virtual void buildSeats() = 0;
        virtual void buildBrake() = 0;
    
    protected:
        Car *car;
    };
    
    class Car1Builder : public CarBuilder {
    public:
        void buildWindow() override {
            car->setWindow("2024 Window");
        }
    
        void buildWheels() override {
            car->setWheels("18 inch Alloy Wheels");
        }
    
        void buildSeats() override {
            car->setSeats(2);
        }
    
        void buildBrake() override {
            car->setBrake("2011 Brake");
        }
    };
    
    class Car2Builder : public CarBuilder {
    public:
        void buildWindow() override {
            car->setWindow("2021 Window");
        }
    
        void buildWheels() override {
            car->setWheels("14 inch Alloy Wheels");
        }
    
        void buildSeats() override {
            car->setSeats(2);
        }
    
        void buildBrake() override {
            car->setBrake("2020 Brake");
        }
    };
    
    class CarDirector {
    public:
        void setCarBuilder(CarBuilder *builder) {
            this->builder = builder;
        }
    
        Car* getCar() {
            return builder->getCar();
        }
    // 객체 생성 단계 명시
    // 여러 인자를 나열하는 방식에서 벗어나 메서도 체이닝이 가능하다.
        void constructCar() {
            builder->createNewCar();
            builder->buildWindow();
            builder->buildWheels();
            builder->buildSeats();
            builder->buildBrake();
        }
    
    private:
        CarBuilder *builder;
    };
    
    int main() {
        CarDirector director;
    
        Car1Builder sportsCarBuilder;
        director.setCarBuilder(&sportsCarBuilder);
        director.constructCar();
    
        Car *Car1 = director.getCar();
        Car1->show();
    
        Car2Builder familyCarBuilder;
        director.setCarBuilder(&familyCarBuilder);
        director.constructCar();
    
        Car *Car2 = director.getCar();
        Car2->show();
    
        delete Car1;
        delete Car2;
    
        return 0;
    }

     

    장점

    1. 객체 생성의 단계적 구성:
      • 객체의 생성 과정을 단계별로 나눌 수 있어, 각 단계에서 필요한 설정을 수행할 수 있습니다.
      • 클라이언트 코드가 어떤 단계에서든 객체의 특정 부분을 생성할 수 있습니다.
    2. 유연한 객체 생성:
      • 다양한 구성의 객체를 동일한 생성 과정으로 만들 수 있어 코드의 재사용성을 높입니다.
      • 동일한 빌더 인터페이스를 사용하여 여러 가지 변형된 객체를 생성할 수 있습니다.
    3. 복잡한 객체 생성의 단순화:
      • 복잡한 객체의 생성 과정을 단순화하고, 생성 로직을 한 곳에 집중시켜 관리할 수 있습니다.
      • 빌더 패턴을 사용하면 객체 생성 코드가 객체의 내부 표현과 분리되므로, 생성 로직을 더 잘 관리하고 유지보수할 수 있습니다.
    4. 불변 객체 생성에 유용:
      • 빌더 패턴을 사용하면 불변 객체를 쉽게 생성할 수 있으며, 이는 멀티스레딩 환경에서 안전성을 제공합니다.
    5. 가독성 향상:
      • 객체 생성 과정이 명확하게 드러나므로, 코드의 가독성과 이해도가 높아집니다.
      • 메서드 체이닝을 통해 직관적이고 읽기 쉬운 코드 작성을 도와줍니다.

    단점

    1. 복잡성 증가:
      • 간단한 객체 생성에 대해서도 불필요하게 복잡한 구조를 도입할 수 있습니다.
      • 빌더 클래스와 디렉터 클래스 등 여러 클래스를 추가로 작성해야 하므로, 코드가 길어지고 복잡해질 수 있습니다.
    2. 메모리 오버헤드:
      • 빌더 객체와 최종 객체를 별도로 유지해야 하므로, 메모리 사용량이 증가할 수 있습니다.
    3. 객체 생성 시간이 증가할 수 있음:
      • 단계별로 객체를 생성하고 설정하기 때문에, 객체 생성에 시간이 더 걸릴 수 있습니다.
    4. 일관성 유지가 어려울 수 있음:
      • 모든 필수 필드를 설정하지 않고 객체를 생성하게 되면, 일관성 문제가 발생할 수 있습니다. 이를 방지하기 위해서는 적절한 검증 로직을 추가해야 합니다.
    5. 복잡한 객체를 위한 추가 코드 필요:
      • 복잡한 객체를 생성할 때 필요한 설정 메서드를 많이 작성해야 하므로, 빌더 클래스의 구현이 복잡해질 수 있습니다.

    빌더 패턴은 객체 생성의 복잡성을 줄이고, 생성 과정의 유연성을 높이는 데 매우 유용합니다. 그러나 간단한 객체에는 불필요하게 복잡성을 증가시킬 수 있으므로, 사용 시에는 필요성과 상황을 잘 고려해야 합니다.

    참고 : chatGPT

    '프로그래밍 지식 > 디자인 패턴' 카테고리의 다른 글

    MVC 패턴 적용  (0) 2021.08.31
Designed by Tistory.