내일배움캠프 3-1 강의에서 디자인 패턴 내용을 간단하게 배워 '포르잔 C++ 바이블' 책에 있는 디자인 패턴 내용 중 생성 패턴에 대해 정리하였습니다.
생성 패턴(creational pattern) : 전통적인 방법으로 인스턴스화하면 안 되는 상황에서, 어떤 형태로 객체를 인스턴스화해야 하는지에 대한 방법
1. 생성 패턴 구조
객체 지향 프로그래밍은 원래 위 그림처럼 객체의 설계와 생성을 분리하는 것이 목적입니다. 그런데 클래스를 설계자가 설계한대로 사용자가 사용하지 않는 경우가 많이 발생했습니다. 그래서 객체의 생성과 관련된 모든 책임을 클래스의 사용자에게 맡기는 것이 좋지 않다는 의견이 나왔습니다.
설계자와 사용자 사이에서 발생할 수 있는 이러한 객체의 생성과 관련된 문제를 해결할 수 있도록 여러 가지 생성 패턴이 만들어졌습니다.
2. 싱글턴 패턴
싱글턴 패턴(singleton pattern)은 사용자가 클래스의 인스턴스를 하나만 생성하게 강제합니다. 싱글턴 패턴은 시스템에서 컨트롤러 또는 매니저(관리자) 역할을 하는 객체를 만들 때 활용합니다.
클래스의 생성자가 한 번만 호출되게 강제해야 합니다. 따라서 인스턴스 생성을 위한 중간 함수를 만들고, 여기에 조건문 등을 활용해서 함수의 호출 횟수를 제한해 줘야 합니다.
- 인스턴스가 있어야 인스턴스 멤버를 호출할 수 있는데, create 함수가 인스턴스를 생성하는 함수이므로 처음 호출할 때는 인스턴스가 존재할 수 없습니다. 따라서 객체를 생성하기 위한 create 함수가 싱글턴 객체에 포함될 경우, 정적 함수로 만들어져야 합니다.
- 생성자를 private으로 선언해 사용자가 직접적으로 접근할 수 없게 만듭니다. 생성자는 create 함수에서 호출합니다.
- 다음과 같은 2가지 방법 중 하나로 객체가 중복 생성되는 것을 막습니다
- 정적 멤버 counter를 생성하고, 처음에 이를 0으로 초기화. create 함수는 create가 0이면 객체를 생성하고, 1이면 객체를 생성하지 않는다
- 정적 멤버 ptr을 생성하고, 처음에 이를 널 포인터로 초기화. create 함수가 호출되는 시점에 ptr이 널 포인터라면 객체를 힙에 생성하고, 생성된 객체에 대한 포인터를 ptr에 할당. 만약 아니라면 객체를 생성하지 않는다.
class Singleton
{
private:
static Singleton* ptr;
Singleton();
public:
static Singleton* create();
};
예제 코드
인터페이스 파일(president.h)
/**************************************************************
* President 클래스의 인터페이스 파일 *
**************************************************************/
#ifndef PRESIDENT_H
#define PRESIDENT_H
#include <iostream>
#include <string>
using namespace std;
// President 클래스의 정의
class President
{
private:
string name;
static President* ptr; // 정적 변수
President(string name); // private 생성자
public:
static President* create(string name); // 정적 함수
string getName() const;
};
#endif
구현 파일(president.cpp)
/**************************************************************
* President 클래스의 구현 파일 *
**************************************************************/
#include "president.h"
// 정적 변수의 정의
President* President::ptr = 0;
// private 생성자의 정의
President::President(string nm)
: name(nm)
{
cout << "대표가 선택되었습니다." << endl;
}
// create 정적 함수의 정의
President* President::create(string name)
{
if(!ptr)
{
ptr = new President(name);
}
else
{
cout << "이미 대표가 존재합니다!" << endl;
}
return ptr;
}
// 접근자 함수
string President::getName() const
{
return name;
}
애플리케이션 파일(app.cpp)
/**************************************************************
* President 클래스를 사용하는 애플리케이션 파일 *
**************************************************************/
#include "president.h"
int main()
{
// 포인터 만들기
President* ptr;
// 첫 번째 대표 객체 생성
ptr = President::create("Mary");
cout << "대표의 이름 = " << ptr->getName() << endl;
// 두 번째 대표 객체 생성 시도
ptr = President::create("John");
cout << "대표의 이름 = " << ptr->getName() << endl;
return 0;
}
3. 팩토리 메소드 패턴
팩토리 메소드 패턴(factory method pattern)을 사용하면 사용자는 최소의 정보만 제공하면, 적합한 객체를 인스턴스화할 수 있습니다. 참고로 이때 메소드라는 것은 멤버 함수를 의미하는 말입니다.
Product 클래스는 순수 가상 함수를 갖는 인터페이스입니다. 이러한 순수 가상 함수는 구체 클래스에서 정의합니다. 인터페이스는 사용자가 호출할 수 있는 정적 멤버 함수 factory를 갖습니다. 베이스 클래스의 factory 함수는 매개변수로 전달된 문자열을 기반으로 적절한 파생 클래스의 factory 함수를 호출해서 객체를 생성해줍니다. 파생 클래스의 생성자는 private으로 만들어서 사용자가 직접적으로 호출할 수 없게 합니다. 파생 클래스의 factory 함수는 자기 자신의 생성자를 호출해서 인스턴스화합니다.
class Product
{
public:
static Product* factory(...);
virtual void function() = 0;
};
class AProduct : public Product
{
private:
AProduct();
public:
static Product* factory();
void function();
};
예제 코드
인터페이스 파일(factory.h)
/**************************************************************
* Shape 추상 클래스, *
* Square과 Circle 구체 클래스의 인터페이스 파일 *
**************************************************************/
#ifndef FACTORY_H
#define FACTORY_H
#include <string>
#include <iostream>
#include <cassert>
using namespace std;
// Shape 베이스 클래스의 정의
class Shape
{
public:
static Shape* factory(string);
virtual void draw() = 0 ;
virtual ~Shape();
};
// Square 클래스의 정의
class Square : public Shape
{
private:
Square();
public:
static Shape* factory();
void draw();
};
// Circle 클래스의 정의
class Circle : public Shape
{
private:
Circle();
public:
static Shape* factory();
void draw();
};
#endif
구현 파일(factory.cpp)
/**************************************************************
* Shape 추상 클래스, *
* Square과 Circle 구체 클래스의 구현 파일 *
**************************************************************/
#include "factory.h"
// Shape 클래스의 정적 factory 함수 정의
Shape* Shape::factory(string type)
{
if(type == "square")
{
return (Square::factory());
}
else if(type == "circle")
{
return (Circle::factory());
}
else
{
cout << "주어진 형태의 도형 객체를 생성할 수 없습니다.";
assert(false);
}
}
// Shape 클래스의 가상 소멸자 정의
Shape::~Shape()
{
}
// Square 클래스의 생성자 정의
Square::Square()
{
// 초기화와 관련된 처리
}
// Square 클래스의 정적 factory 함수 정의
Shape* Square::factory()
{
return new Square();
}
// Square 클래스의 draw 함수 정의
void Square::draw()
{
cout << "Square 객체를 그렸습니다." << endl;
}
// Circle 클래스의 정적 factory 함수 정의
Shape* Circle::factory()
{
return new Circle();
}
// Circle 클래스의 생성자 정의
Circle::Circle()
{
// 초기화와 관련된 처리
}
// Circle 클래스의 draw 함수 정의
void Circle::draw()
{
cout << "Circle 객체를 그렸습니다." << endl;
}
애플리케이션 파일(app.cpp)
/**************************************************************
* 팩토리 메소드 패턴을 사용하는 애플리케이션 파일 *
**************************************************************/
#include "factory.h"
using namespace std;
int main()
{
// 포인터 생성
Shape* shape;
// Square 객체 생성하고 사용
shape = Shape::factory("square");
shape -> draw();
// Circle 객체 생성하고 사용
shape = Shape::factory("circle");
shape -> draw();
return 0;
}
4. 빌더 패턴
빌더 패턴(builder pattern)은 객체의 생성과 객체의 표현 형태를 분리해서 사용자가 쉽게 사용할 수 있게 해줍니다.
Director라는 구체 클래스, Builder라는 추상 클래스, Builder1~N이라는 구체 파생 클래스가 있습니다. 이때 Builder1~N이 사용자가 필요로 하는 객체입니다.
사용자는 create 함수를 호출하고, 매개변수로 필요한 객체의 종류를 지정합니다. 구체 파생 클래스 Builder1~N은 베이스 추상 클래스 Builder로부터 funct1~n까지의 함수를 상속받습니다. 이때 필요한 것만 구현하며, 필요 없는 것은 구현을 비워둡니다.
예제 코드
인터페이스 파일(builder.h)
/**************************************************************
* 빌더 패턴의 인터페이스 파일 *
**************************************************************/
#ifndef BUILDER_H
#define BUILDER_H
#include <string>
#include <iostream>
#include <cassert>
using namespace std;
// 모든 패키지의 베이스 클래스가 되는 Vacation 추상 클래스
class Vacation
{
public:
virtual void bookHotels() = 0;
virtual void bookFlights() = 0;
virtual void bookTours() = 0;
};
// 첫 번째 구체 클래스
class Package1 : public Vacation
{
public:
void bookHotels();
void bookFlights();
void bookTours();
};
// 두 번째 구체 클래스
class Package2 : public Vacation
{
public:
void bookHotels();
void bookFlights();
void bookTours();
};
// 세 번째 구체 클래스
class Package3 : public Vacation
{
public:
void bookHotels();
void bookFlights();
void bookTours();
};
// 정적 멤버를 갖는 Director 클래스
class Director
{
public:
static Vacation* vacation;
static void book(int type); // 클라이언트는 이 함수만 호출 가능
};
#endif
구현 파일(builder.cpp)
/**************************************************************
* 빌더 패턴의 구현 파일 *
**************************************************************/
#include "builder.h"
// 정적 데이터 멤버 정의
Vacation* Director::vacation = 0;
// 클라이언트가 호출할 정적 멤버 함수 정의
void Director::book(int packageType)
{
if(packageType == 1)
{
vacation = new Package1();
}
else if(packageType == 2)
{
vacation = new Package2();
}
else if(packageType == 3)
{
vacation = new Package3();
}
cout << "여행 계획에 대한 정보: " << endl;
vacation->bookHotels();
vacation->bookFlights();
vacation->bookTours();
cout << endl;
}
// Package1의 멤버 함수 정의
void Package1::bookHotels()
{
cout << "호텔 예약" << endl;
}
void Package1::bookFlights()
{
cout << "항공권 예약" << endl;
}
void Package1::bookTours() // 비워둠
{
}
// Package2의 멤버 함수 정의
void Package2::bookHotels()
{
cout << "호텔 예약" << endl;
}
void Package2::bookFlights() // The body is empty
{
}
void Package2::bookTours()
{
cout << "투어 예약" << endl;
}
// Package3의 멤버 함수 정의
void Package3::bookHotels()
{
cout << "호텔 예약" << endl;
}
void Package3::bookFlights()
{
cout << "항공권 예약" << endl;
}
void Package3::bookTours()
{
cout << "투어 예약" << endl;
}
애플리케이션 파일(app.cpp)
/**************************************************************
* 빌더 패턴을 사용하는 애플리케이션 파일 *
**************************************************************/
#include "builder.h"
int main()
{
// 여행 유형 선택
int type;
do
{
cout << "여행 유형을 입력해주세요(1, 2, 3): ";
cin >> type;
} while(type < 1 || type > 3);
// 여행 계획 확인
Director::book(type);
return 0;
}
5. 프로토타입 패턴
프로토타입 패턴(prototype pattern)은 사용자가 객체를 많이 만들어야 할 때 사용합니다. 사용자는 각 패턴의 객체를 하나만 만들고, 이를 복제해서 다른 객체를 만듭니다. 복사 생성자를 활용하는 것과 비슷하지만, 다형성이 적용되어 있을 때는 복사 생성자를 활용할 수 없으므로 프로토타입 패턴을 사용해야 합니다.
추상 클래스에서 모든 파생 클래스에서 오버라이드할 clone함수를 정의합니다. clone 함수를 사용하면(clone 함수 내부에서 복사 생성자를 호출하므로), 별도의 인스턴스화와 초기화 없이 객체를 만들어낼 수 있습니다.
복제는 원본 대상이 있어야 하므로, 첫 번째 객체는 생성자를 호출해서 만들어야 합니다.
예제 코드
인터페이스 파일(vehicle.h)
/**************************************************************
* 빌더 패턴을 사용하는 애플리케이션 파일 *
**************************************************************/
#ifndef VEHICLE_H
#define VEHICLE_H
#include <string>
#include <iostream>
using namespace std;
// Vehicle 추상
class Vehicle
{
public:
virtual Vehicle* clone() const = 0;
virtual ~Vehicle(){}
};
// Car 구체 클래스
class Car : public Vehicle
{
private:
string model;
string color;
public:
Car(string model, string color);
Car(const Car& car);
Car* clone() const;
};
// Truck 구체 클래스
class Truck : public Vehicle
{
private:
string size;
string color;
public:
Truck(string size, string color);
Truck(const Truck& truck);
Truck* clone() const;
};
#endif
구현 파일(vehicle.cpp)
/**************************************************************
* 멤버 함수를 구현하는 구현 파일 *
**************************************************************/
#include "vehicle.h"
// Car의 생성자
Car::Car(string m, string c)
:model(m), color(c)
{
cout << color << " " << model << " 자동차를 만들었습니다." << endl;
}
// Car의 복사 생성자
Car::Car(const Car& car)
:model(car.model), color(car.color)
{
cout << color << " " << model << " 자동차를 만들었습니다." << endl;
}
// Car의 clone 멤버 함수
Car* Car::clone() const
{
return new Car(*this);
}
// Truck의 생성자
Truck::Truck(string s, string c)
:size(s), color(c)
{
cout << color << " " << size << " 트럭을 만들었습니다." << endl;
}
// Truck의 복사 생성자
Truck::Truck(const Truck& truck)
:size(truck.size), color(truck.color)
{
cout << color << " " << size << " 트럭을 만들었습니다." << endl;
}
// Truck의 clone 멤버 함수
Truck* Truck::clone() const
{
return new Truck(*this);
}
애플리케이션 파일(app.cpp)
/**************************************************************
* The application file to test the Prototype Pattern *
**************************************************************/
#include "vehicle.h"
int main()
{
// 차량 4대 생성
Vehicle* veh1 = new Car("세단", "흰색"); // 원본
Vehicle* veh2 = veh1->clone(); // 복제
Vehicle* veh3 = new Truck("큰", "검정색"); // 원본
Vehicle* veh4 = veh3->clone(); // 복제
// 차량 파괴
delete veh1;
delete veh2;
delete veh3;
delete veh4;
return 0;
}
출처 : 포르잔 C++ 바이블
'내일배움캠프 > TIL' 카테고리의 다른 글
[내일배움캠프 Day16] C++ 3주차 과제 진행 (1) | 2025.01.09 |
---|---|
[내일배움캠프 Day15] 디자인 패턴 - 구조 패턴 (2) | 2025.01.09 |
[내일배움캠프 Day13] C++ 2주차 과제 진행 (1) | 2025.01.03 |
[내일배움캠프 Day12] 반복자와 포인터 (2) | 2025.01.02 |
[내일배움캠프 Day11] 템플릿 (3) | 2024.12.31 |