본문 바로가기
CS 전공지식 정리/디자인 패턴

[디자인 패턴] 팩토리 패턴(Factory Pattern)

by code_killer 2023. 10. 22.
728x90
반응형

1. 팩토리 패턴(Factory Pattern)이란?

  • 객체를 생성하는 클래스를 따로 두는 디자인 패턴(Simple Factory Pattern)
#include <iostream>
#include <memory>

using namespace std;

class Animal {
public:
	virtual void makeSound() = 0;
};

class Dog : public Animal {
public:
	void makeSound() override {
		cout << "Woof!" << endl;
	}
};

class Cat : public Animal {
public:
	void makeSound() override {
		cout << "Meow!" << endl;
	}
};

class AnimalFactory {
public:
	static unique_ptr<Animal> createAnimal(const string& type) {
		if (type == "Dog") return make_unique<Dog>();
		else if (type == "Cat") return make_unique<Cat>();
		return nullptr;
	}
};

int main() {
	auto dog = AnimalFactory::createAnimal("Dog");
	dog->makeSound();

	auto cat = AnimalFactory::createAnimal("Cat");
	cat->makeSound();

	return 0;
}

 

1) 팩토리 패턴의 장점

  1. 객체 생성과 사용 분리

   - 클라이언트 코드는 객체 생성 로직을 몰라도 되서, 코드의 결합도가 낮아지고 유지보수성이 향상

 2. 코드 중복 최소화

  - 객체 생성 코드가 한 곳(팩토리)에 집중되므로, 동일한 객체를 여러 번 만들 필요가 없음

 3. 유연성 및 확장성 증가

  - 새로운 클래스 타입을 추가하더라도 기존 코드에 영향을 주지 않음(Open/Closed 원칙 OCP)에 부합

 4. 조건부 로직 감소

  - 팩토리는 요청에 따라 적절한 객체를 반환하므로, 클라이언트 쪽에서 많은 조건부 로직(if-else 또는 switch-case 문 등)을 사용하지 않아도 됨

 

2) 팩토리 패턴의 단점

 1. 클래스 수 증가

  - 각각의 제품마다 별도의 팩토리 클래스를 만들어야 할 수 있으므로, 시스템 내 클래스 수가 증가

 2. 코드 복잡성 증가

  - 초기 학습 곡선이 가파르며, 설계 자체가 복잡해져서 이해하기 어려울 수 있음

 3. 클래스 타입 변경 시 수정 필요

  - 제공하는 제품(객체) 종류나 방식이 변경될 경우, 해당 팩토리 클래스를 수정해야함.

 

2. 팩토리 메소드 패턴(Factory Method Pattern)

  • 객체를 생성하는 공장 메소드를 인터페이스로 정의하고, 서브 클래스에서 이 메소드를 오버라이딩하여 구체적인 객체를 생성하는 디자인 패턴
#include <iostream>
#include <memory>

using namespace std;

class Animal {
public:
	virtual void makeSound() = 0;
};

class Dog : public Animal {
public:
	void makeSound() override {
		cout << "Woof!" << endl;
	}
};

class Cat : public Animal {
public:
	void makeSound() override {
		cout << "Meow!" << endl;
	}
};

class AnimalCreator {
public:
	virtual unique_ptr<Animal> createAnimal() = 0;
};

class DogCreator : public AnimalCreator {
public:
	unique_ptr<Animal> createAnimal() override {
		return make_unique<Dog>();
	}
};

class CatCreator : public AnimalCreator {
public:
	unique_ptr<Animal> createAnimal() override {
		return make_unique<Cat>();
	}
};

int main() {
	DogCreator dog_creator;
	auto dog = dog_creator.createAnimal();
	dog->makeSound();

	CatCreator cat_creator;
	auto cat = cat_creator.createAnimal();
	cat->makeSound();

	return 0;
}

 

1) Factory Method Pattern을 사용하는 경우

 1. 객체가 생성되는 과정이 복잡하거나 클라이언트에게 노출되어서는 안 될 때

 - 팩토리 메소드를 통해 이를 캡슐화할 수 있으며, 클라이언트는 단순히 팩토리 메소드를 호출하기만 하면 되므로, 내부 로직에 대해 알 필요가 없음

 2. 객체 타입을 실행 시간에 결정해야 할 때

 - 팩토리 메소드는 실행 시간에 어떤 클래스의 인스턴스를 생성할지 결정할 수 있음. 따라서 프로그램의 동작을 런타임에서 변경하고 확장하는 것이 가능

 3. 코드의 결합도를 낮추고 싶을 때

  - 객체 생성 코드와 사용 코드 사이의 의존성을 줄여줌. 이로 인해 한 부분의 변경이 다른 부분에 영향을 미치는 정도가 줄어들며, 유지보수와 테스트가 용이

 

3. 추상 팩토리 패턴(Abstract Factory Pattern)

  • 관련된 객체의 집합을 생성하기 위한 인터페이스를 제공하는 디자인 패턴
#include <iostream>
#include <memory>

using namespace std;

class Animal {
public:
	virtual void makeSound() = 0;
};

class Dog : public Animal {
public:
	void makeSound() override {
		cout << "Woof!" << endl;
	}
};

class Cat : public Animal {
public:
	void makeSound() override {
		cout << "Meow!" << endl;
	}
};

class Food {
public:
	virtual void eat() = 0;
};

class DogFood : public Food {
public:
	void eat() override {
		cout << "Dog is eating dog food." << endl;
	}
};

class CatFood : public Food {
public:
	void eat() override {
		cout << "Cat is eating cat food." << endl;
	}
};

// Abstract Factories
class AnimalFactory {
public:
	virtual unique_ptr<Animal> createAnimal() = 0;
};

class FoodFactory {
public:
	virtual unique_ptr<Food> createFood() = 0;
};

// Concrete Factories
class DogFactory : public AnimalFactory {
public:
	unique_ptr<Animal> createAnimal() {
		return make_unique<Dog>();
	}
};

class CatFactory : public AnimalFactory {
public:
	unique_ptr<Animal> createAnimal() {
		return make_unique<Cat>();
	}
};

class DogFoodFactory : public FoodFactory {
public:
	unique_ptr<Food> createFood() {
		return make_unique<DogFood>();
	}
};

class CatFoodFactory : public FoodFactory {
public:
	unique_ptr<Food> createFood() {
		return make_unique<CatFood>();
	}
};

class PetFactory {
public:
	static void create_pet(AnimalFactory& Af, FoodFactory& Ff) {
		unique_ptr<Animal> pet = Af.createAnimal();
		pet->makeSound();
		unique_ptr<Food> pet_food = Ff.createFood();
		pet_food->eat();
	}
};

int main() {
	DogFactory df;
	DogFoodFactory dff;
	PetFactory::create_pet(df, dff);

	CatFactory cf;
	CatFoodFactory cff;
	PetFactory::create_pet(cf, cff);

	return 0;
}

 

 1) 추상 팩토리 패턴을 사용하는 경우

 1. 관련된 객체의 집합을 생성할 때

  - 추상 팩토리 패턴은 관련된 객체들을 함꼐 생성하는 것에 중점을 두는 패턴. 예를 들어, GUI 애플리케이션에서 여러 플랫폼(Windows, Linux 등)에 대한 일관된 사용자 인터페이스를 제공해야 하는 경우 각 플랫폼별로 버튼, 메뉴 등의 위젯을 생성하는 별도의 팩토리 클래스 생성 가능

 2. 시스템이 독립적으로 확장 가능해야 할 때

  - 추상 팩토리 패턴은 새로운 종류의 제품을 추가하기 쉽게 가능. 새로운 제품군(즉, 관련된 객체들의 집합)이 추가되더라도 기존 코드는 변경할 필요가 없으며, 단지 새로운 팩토리 클래스만 추가하면 됩니다.

 3. 클래스 라이브러리를 제공하고, 그 구현 내용을 감춰야 할 때

  - 클라이언트 코드는 추상 팩토리와 추상 제품 인터페이스만 알고 있으면 되므로 구현 세부사항에 대해서는 신경 쓸 필요가 없음

 4. 제공되는 클래스들이 서로 연관되거나 의존성을 가질 때

  - 한 객체가 다른 객체 없이는 의미가 없거나 정상적으로 동작하지 않는 경우, 추상 팩토리 패턴을 통해 연관성 잇는 객체들을 함꼐 생성하여 오류 가능성을 줄일 수 있

728x90