Creational Pattern(생성 패턴)이란 객체 인스턴스를 생성하는 패턴으로, 클라이언트와 그 클라이언트가 생성해야 하는 객체 인스턴스 사이의 연결을 끊어 주는 패턴이다. 그 종류에는 싱글턴, 추상 팩토리, 팩토리 메소드, 프로토 타입, 빌더가 있다.
싱글턴 패턴(Singleton Pattern)
- 특정 클래스에 객체 인스턴스가 하나만 만들어지도록 해주는 패턴
- 싱글턴 패턴을 사용하면 전역 변수를 사용할 때와 마찬가지로 객체 인스턴스를 어디서든지 액세스 할 수 있게 만들 수 있다
- 클래스 인스턴스를 하나만 만들고 그 인스턴스로의 전역 접근을 제공한다
- 보통 싱글턴 클래스가 속성으로 인스턴스를 가지고,
getInstance()
라는 메서드를 제공하여 접근을 허용한다.
요구사항 예제
- 개발 중인 시스템에서 스피커에 접근할 수 있는 클래스를 만들어라
- 스피커는 한 개 뿐이다.
SystemSpeaker
public class SystemSpeaker {
static private SystemSpeaker instance;
private int volume;
private SystemSpeaker() {
volume = 5;
}
public static SystemSpeaker getInstance() {
if (instance == null) {
instance = new SystemSpeaker();
System.out.println("새로 생성");
} else {
System.out.println("이미 생성");
}
return instance;
}
public int getVolume() {
return volume;
}
public void setVolume(int volume) {
this.volume = volume;
}
}
Main
public class Main {
public static void main(String[] args) {
SystemSpeaker speaker1 = SystemSpeaker.getInstance();
SystemSpeaker speaker2 = SystemSpeaker.getInstance();
// 5, 5
System.out.println(speaker1.getVolume());
System.out.println(speaker2.getVolume());
speaker1.setVolume(10);
// 10, 10
System.out.println(speaker1.getVolume());
System.out.println(speaker2.getVolume());
}
}
실행 결과
새로 생성
이미 생성
5
5
10
10
speaker1과 speaker2는 디버깅 해보면 완벽히 동일한 주소를 가지고 있다. 즉, 두 객체는 동일한 객체이다! 하나의 객체가 공유되는 모습을 확인할 수 있다.
프로토타입 패턴(Prototype Pattern)
- 프로토타입(Protoytype): 원형. 실제 객체를 만들기에 앞서 테스트를 위한 샘플
- 프로토타입 인스턴스를 사용하여 생성할 객체의 종류를 명시하고, 이렇게 만든 견본을 복사해서 새로운 객체를 생성한다 = 원본 객체를 새로운 객체에 복사하여 필요에 따라 수정하는 메커니즘
- 프로토타입패턴은 복사를 위해 자바에서 제공하는
clone
메소드를 사용 - 생산 비용이 높은 인스턴스를 복사를 통해서 쉽게 생성할 수 있도록 함
- '인스턴스 생산 비용이 높다'의 의미
- 종류가 너무 많아서 클래스로 정리되지 않는 경우
- 클래스로부터 인스턴스 생성이 어려운 경우
- '인스턴스 생산 비용이 높다'의 의미
- 프로토타입 패턴은 객체의 생성, 복합, 표현 방법에 독립적인 제품을 만들고자 할 때 사용한다
- 객체를 생성하는데 비용이 많이 들고, 비슷한 객체가 이미 있는 경우
- 인스턴스화할 클래스를 런타임에 지정할 때(이를테면, 동적 로딩)
- 객체 클래스 계통과 병렬적으로 만드는 팩토리 클래스를 피하고 싶을 때
- 클래스의 인스턴스들이 서로 다른 상태 조합 중에 어느 하나일 때
요구사항 예제
- 일러스트레이터와 같은 그림 그리기 툴을 개발 중이다.
- 어떤 모양(Shape)을 그릴 수 있도록 하고 복사 붙여넣기 기능을 구현하라
- 이때, 두 도형이 겹치지 않도록 복사된 객체의 위치를 살짝 옆으로 이동시켜라
Shape
public class Shape implements Cloneable {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class Circle extends Shape {
private int x, y, r;
public Circle(int x, int y, int r) {
this.x = x;
this.y = y;
this.r = r;
}
public Circle copy() throws CloneNotSupportedException {
Circle circle = (Circle) clone();
circle.setX(circle.getX() + 1);
circle.setY(circle.getY() + 1);
return circle;
}
// getter, setter 생략...
}
- Java에서는 Cloneable을 구현함으로써 clone() 메서드를 자동으로 제공한다.
Main
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Circle circle1 = new Circle(1, 1, 3);
Circle circle2 = circle1.copy();
// 1, 1, 3
System.out.println(circle1.getX() + ", " + circle1.getY() + ", " + circle1.getR());
// 2, 2, 3
System.out.println(circle2.getX() + ", " + circle2.getY() + ", " + circle2.getR());
}
}
실행 결과
1, 1, 3
2, 2, 3
다른 예제도 한번 봐보자!
Employees
public class Employees implements Cloneable {
private List <String> empList;
public Employees() {
empLisy = new ArrayList <> ();
}
public Employees(List <String> list) {
this.empList = list;
}
// DB 접근이라고 가정!
public void loadData() {
empList.add("Ann");
empList.add("David");
empList.add("John");
empList.add("Methew");
}
@Override
public Object clone() throws CloneNotSupportedException {
List < String > temp = new ArrayList <> ();
for (String str: this.empList) {
temp.add(str);
}
return new Employees(temp);
}
}
Main
public class PrototypePattern {
public static void main(String[] args) throws CloneNotSupportedException {
Employees emps = new Employees();
emps.loadData(); // Ann, John, Methew...
Employees emps1 = (Employees) emps.clone();
Employees emps2 = (Employees) emps.clone();
List < String > list1 = emps1.getEmpList();
list1.add("Peter");
List < String > list2 = emps2.getEmpList();
list2.remove("John");
System.out.println("emps: " + emps.getEmpList());
System.out.println("emps1: " + list1.getEmpList());
System.out.println("emps2: " + list2.getEmpList());
}
}
- Employee 클래스는 loadData를 통해 DB에 접근하여 데이터를 받아오고 받아온 데이터를 저장하고 있다.
- Employee 객체에 대한 clone을 구현해주어 매번 new를 통해 객체를 생성하고 loadData로 받아올 필요가 없어졌다!
얕은 복사 vs 깊은 복사
clone() 메서드 없이, 단순히
A a1 = new A();
애후A a2 = a1;
을 통해 대입해주기만 한다면 이는 얕은 복사가 일어난다. 얕은 복사란 값을 복사하는 것이 아닌 주소를 복사하여 a1과 a2과 완전히 동일한 메모리 주소를 가리키게 하는 것이다. 따라서, a1에 수정을 가하면 a2에도 수정이 발생하여 의도하지 않은 동작이 발생할 수 있다.반면, A 클래스가
Cloneable
인터페이스를 구현하고 있고,A a2 = a1.clone()
을 한다면, 깊은 복사가 발생하여 주소를 복사하는 것이 아닌 값들만을 넘겨주어 새로운 A를 생성하게 된다. 즉 a1과 a2는 서로 다른 별도의 객체이다.여기서 주의할 점은, 만약 A 클래스 내부에 속성으로 다른 클래스 객체를 갖고있을 경우, 이 객체에 대해서는 깊은 복사를 자동으로 수행해주지 않는다. 따라서, A 클래스의
clone()
메서드를 오버라이드를 통해 직접 구현하여, 이 메서드에서 해당 클래스 객체에 대한 깊은 복사를 구현해주어야 한다.
빌더 패턴(Builder Pattern)
- 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴
- 복잡한 단계를 거쳐야 생성되는 객체의 구현을 서브 클래스에게 넘겨주어 구현한다
= 별도의Builder
클래스를 만들어 메소드를 통해 step-by-step 으로 값을 입력받은 후에 최종적으로 build() 메소드로 하나의 인스턴스를 생성하여 리턴
- 복잡한 단계를 거쳐야 생성되는 객체의 구현을 서브 클래스에게 넘겨주어 구현한다
- 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식
- 장점
- 생성자 방식에 비해 객체 생성 과정을 일관된 프로세스로 표현
- 디폴트 매개변수 생략을 간접적으로 지원
- 빌더라는 객체 생성 전용 클래스를 경유하여 이용함으로써 디폴트 매개변수가 설정된 필드를 설정하는 메서드를 호출하지 않는 방식으로 마치 디폴트 매개변수를 생략하고 호출하는 효과를 간접적으로 구현
- 필수 멤버와 선택적 멤버를 분리 가능
- 초기화가 필수인 멤버는 빌더의 생성자로 받게 하여 필수 멤버를 설정해주어야 빌더 객체가 생성되도록 유도하고, 선택적인 멤버는 빌더의 메서드로 받도록 하면, 사용자로 하여금 필수 멤버와 선택 멤버를 구분하여 객체 생성을 유도
- 객체 생성 단계를 지연할 수 있음
- 초기화 검증을 멤버별로 분리
- 멤버에 대한 변경 가능성 최소화를 추구
- 단점
- 코드 복잡성 증가
- 생성자 보다는 성능은 떨어진다
- 지나친 빌더 남용은 금지
- 클래스의 필드의 개수가 4개 보다 적고, 필드의 변경 가능성이 없는 경우라면 차라리 생성자나 정적 팩토리 메소드를 이용하는 것이 더 좋을 수 있다
구현 자체는 크게 어렵지 않다!
Student
public class Student {
private int id;
private String name = "아무개";
private int grade = 1;
private String phoneNumber = "010-0000-0000";
public Student(int id, String name, int grade, String phoneNumber) {
this.id = id;
this.name = name;
this.grade = grade;
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", grade=" + grade +
", phoneNumber='" + phoneNumber + '\'' +
'}';
}
public static class Builder {
private int id;
private String name;
private int grade;
private String phoneNumber;
public Builder() {
}
public Builder(Student student) {
this.id = student.id;
this.name = student.name;
this.grade = student.grade;
this.phoneNumber = student.phoneNumber;
}
public Builder id(int id) {
this.id = id;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder grade(int grade) {
this.grade = grade;
return this;
}
public Builder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public Student build() {
return new Student(id, name, grade, phoneNumber); // Student 생성자 호출
}
}
}
Main
public class BuilderMain {
public static void main(String[] args) {
Student student = new Student.Builder()
.id(1)
.name("name")
.grade(1)
.phoneNumber("01000000000")
.build();
System.out.println("student = " + student);
}
}
- 빈 생성자 호출 후, setter를 통해 하나하나 설정해주는 방식이나 수많은 파라미터들을 넘겨 생성자를 통해 생성해주는 방식보다 훨씬 가독성이 좋고 유지보수하기 편리하다.여러가지의 빌드 형식을 유연하게 처리하는 것에 목적을 둔다
- Student 클래스 내부에 Builder라는 내부 클래스를 두어 해당 클래스를 통해 단계적으로 객체를 생성한다.
여기서 구현한 방식은 Effective Java에서의 빌더 패턴인, 심플 빌더 패턴(Simple Builder Pattern)이다. GoF 디자인 패턴에서 다루는 빌더 패턴은 디렉터 빌더 패턴(Director Builder Pattern)으로 여러가지의 빌드 형식을 유연하게 처리하는 것에 목적을 둔다. 어찌보면 일반적인 빌더 패턴을 고도화 시킨 패턴이라고 볼 수도 있다. 자세한 내용은 다음에 다뤄보도록 하자!
팩토리 메소드 패턴(Factory Method Pattern)
- 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 생성 디자인 패턴
- 제품 객체들을 도맡아 생성하는 공장 클래스를 만들고, 이를 상속하는 서브 공장 클래스의 메서드에서 여러가지 제품 객체 생성을 각각 책임 지는 것
- 객체 생성에 필요한 과정을 템플릿 처럼 미리 구성해놓고, 객체 생성에 관한 전처리나 후처리를 통해 생성 과정을 다양하게 처리하여 객체를 유연하게 정할 수 있는 특징 => 템플릿 메소드 패턴
- 사용 시기
- 클래스 생성과 사용의 처리 로직을 분리하여 결합도를 낮추고자 할 때
- 코드가 동작해야 하는 객체의 유형과 종속성을 캡슐화를 통해 정보 은닉 처리 할 경우
- 라이브러리 혹은 프레임워크 사용자에게 구성 요소를 확장하는 방법을 제공하려는 경우
- 기존 객체를 재구성하는 대신 기존 객체를 재사용하여 리소스를 절약하고자 하는 경우
- 장점
- 생성자(Creator)와 구현 객체(concrete product)의 강한 결합을 피할 수 있다.
- 팩토리 메서드를 통해 객체의 생성 후 공통으로 할 일을 수행하도록 지정해줄 수 있다.
- 캡슐화, 추상화를 통해 생성되는 객체의 구체적인 타입을 감출 수 있다.
- 단일 책임 원칙(SRP) 준수 : 객체 생성 코드를 한 곳 (패키지, 클래스 등)으로 이동하여 코드를 유지보수하기 쉽게 할수 있으므로 원칙을 만족
- 개방/폐쇄 원칙(OCP) 준수 : 기존 코드를 수정하지 않고 새로운 유형의 제품 인스턴스를 프로그램에 도입할 수 있어 원칙을 만족 (확장성 있는 전체 프로젝트 구성이 가능)
- 생성에 대한 인터페이스 부분과 생성에 대한 구현 부분을 따로 나뉘었기 때문에 패키지 분리하여 개별로 여러 개발자가 협업을 통해 개발
- Creator, Product와 같은 생성에 대한 인터페이스(혹은 추상 클래스) 부분은 따로 두고, 이에 대한 구현 클래스도 따로 두어 협업하기 용이
- 단점
- 각 제품 구현체마다 팩토리 객체들을 모두 구현해주어야 하기 때문에, 구현체가 늘어날 때마다 팩토리 클래스가 증가하여 서브 클래스 수가 폭증한다.
- 코드의 복잡성이 증가한다.
팩토리 메서드 패턴 구조
- Creator: 최상위 공장 클래스로서, 팩토리 메서드를 추상화하여 서브 클래스로 하여금 구현하도록 함
- 객체 생성 처리 메서드(someOperation): 객체 생성에 관한 전처리, 후처리를 템플릿화한 메소드
- 팩토리 메서드(createProduct): 서브 공장 클래스에서 재정의할 객체 생성 추상 메서드 (추상 팩토리 패턴과의 차이점!)
- ConcreteCreator: 각 서브 공장 클래스들은 이에 맞는 제품 객체를 반환하도록 생성 추상 메서드를 재정의한다. 즉, 제품 객체 하나당 그에 걸맞는 생산 공장 객체가 위치된다.
- Product: 제품 구현체를 추상화
- ConcreteProduct: 제품 구현체
요구 사항 예제
- 게임 아이템과 아이템 생성을 구현한다
- 아이템을 생성하기 전에 데이터베이스에서 아이템 정보를 요청한다
- 아 데이터베이스에 아이템 생성 정보를 남긴다
- 아이템을 생성하는 주체를 ItemCreator로 이름 짓는다
- 아이템은 item이라는 interface로 다룬다
- item은 use 함수를 기본 함수로 갖고 있다
- 현재 아이템의 종류는 체력 회복 물약, 마력 회복 물약이 있다
Item
public interface Item {
public void use();
}
public class HpPotion implements Item {
@Override
public void use() {
System.out.println("체력 회복!");
}
}
public class MpPotion implements Item {
@Override
public void use() {
System.out.println("마력 회복!");
}
}
ItemCreator
public abstract class ItemCreator {
// 템플릿 메소드와 유사한 형태
public Item createOperation() {
requestItemInfo(); // Step 1
Item item = createItem(); // Step 2
createItemLog(); // Step 3
return item;
}
// 아이템을 생성하기 전에 DB에서 아이템 정보 요청
abstract protected void requestItemInfo();
// 아이템을 생성 후 아이템 복제 등의 불법을 방지하기 위해 DB에 아이템 생성 정보 남김
abstract protected void createItemLog();
// 아이템 생성
abstract protected Item createItem();
}
public class HpPotionCreator extends ItemCreator {
@Override
protected void requestItemInfo() {
System.out.println("DB에서 체력 회복 물약 정보 가져옴");
}
@Override
protected void createItemLog() {
System.out.println("[" + LocalDateTime.now() + "]" + " 체력 회복 물약 새로 생성.. ");
}
@Override
protected Item createItem() {
return new HpPotion();
}
}
public class MpPotionCreator extends ItemCreator {
@Override
protected void requestItemInfo() {
System.out.println("DB에서 마력 회복 물약 정보 가져옴");
}
@Override
protected void createItemLog() {
System.out.println("[" + LocalDateTime.now() + "]" + " 마력 회복 물약 새로 생성.. ");
}
@Override
protected Item createItem() {
return new MpPotion();
}
}
최상위 공장 클래스는 반드시 추상 클래스로 선언할 필요 없다. Java 8 버전 이후 추가된 인터페이스의 디폴트 메서드를 통해 팩토리 메서드를 선언하면 된다.
Main
public class Main {
public static void main(String[] args) {
ItemCreator creator;
Item item;
creator = new HpPotionCreator();
item = creator.createOperation();
item.use();
creator = new MpPotionCreator();
item = creator.createOperation();
item.use();
}
}
실행 결과
DB에서 체력 회복 물약 정보 가져옴
[2024-11-01T10:10:31.186316] 체력 회복 물약 새로 생성..
체력 회복!
DB에서 마력 회복 물약 정보 가져옴
[2024-11-01T10:10:31.195860] 마력 회복 물약 새로 생성..
마력 회복!
객체 생성 로직에 변경 사항이 생겨도 클라이언트에게 노출될 메인 메서드의 코드는 전혀 변경할 필요가 없다.
추상 팩토리 패턴(Abstract Factory Pattern)
- 연관성이 있는 객체 군이 여러개 있을 경우 이들을 묶어 추상화하고, 어떤 구체적인 상황이 주어지면 팩토리 객체에서 집합으로 묶은 객체 군을 구현화 하는 생성 패턴
- 클라이언트에서 특정 객체을 사용할때 팩토리 클래스만을 참조하여 특정 객체에 대한 구현부를 감추어 역할과 구현을 분리시킬 수 있다.
- 핵심은 제품 '군' 집합을 타입 별로 찍어낼수 있다는 점
- 사용해야하는 때
- 관련 제품의 다양한 제품 군과 함께 작동해야 할때, 해당 제품의 구체적인 클래스에 의존하고 싶지 않은 경우
- 여러 제품군 중 하나를 선택해서 시스템을 설정해야하고 한 번 구성한 제품을 다른 것으로 대체할 수도 있을 때
- 제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 노출시키고 싶을 때
- 장점
- 객체를 생성하는 코드를 분리하여 클라이언트 코드와 결합도를 낮출 수 있다.
- 제품 군을 쉽게 대체 할 수 있다.
- 단일 책임 원칙 준수(SRP)
- 개방/폐쇄 원칙 준수(OCP)
- 단점
- 각 구현체마다 팩토리 객체들을 모두 구현 필요.. -> 객체 늘어날때마다 클래스가 증가 -> 코드 복잡성 증가
- 이는 팩토리 패턴의 공통적인 문제점이다
- 기존 추상 팩토리의 세부사항이 변경되면 모든 팩토리에 대한 수정 필요..
- 새로운 종류의 제품을 지원하는 것이 어려움 -> 새로운 제품 추가 시 팩토리 구현 로직 자체를 변경해야 함
- 각 구현체마다 팩토리 객체들을 모두 구현 필요.. -> 객체 늘어날때마다 클래스가 증가 -> 코드 복잡성 증가
구조
AbstractFactory
: 최상위 팩토리 클래스로, 여러개의 제품들을 생성하는 여러 메소드들을 추상화한다.ConcreteFactory
: 서브 팩토리 클래스들은 타입에 맞는 제품 객체를 반환하도록 메소드들을 재정의한다.AbstractProduct
: 각 타입의 제품들을 추상화한 인터페이스ConcreteProduct
: 각 타입의 제품 구현체들로, 이들은 팩토리 객체로부터 생성된다.
Abstract Factory vs Factory Method
- 공통점
- 객체 생성 과정을 추상화한 인터페이스(팩토리)를 제공
- 객체 생성을 캡슐화함으로써 구체적인 타입을 감추고 느슨한 결합 구조를 구성
- 차이점
- 팩토리 메서드 패턴
- 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적
- 한 Factory 당 한 종류의 객체 생성 지원 (Factory : Product = 1:1)
- 메소드 레벨에서 포커스를 맞춰, 클라이언트의
ConcreteProduct
인스턴스의 생성 및 구성에 대한 의존을 감소
- 추상 팩토리 패턴
- 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적
- 한 Factory에서 서로 연관된 여러 종류의 객체 생성을 지원 (Factory : Product = 1 : N)
- 클래스(Factory) 레벨에서 포커스를 맞춰, 클라이언트의
ConcreteProduct
인스턴스 군의 생성 및 구성에 대한 의존을 감소
- 팩토리 메서드 패턴
예시) 버튼, 체크박스, 텍스트박스라는 객체들은 화면 컴포넌트(Component) 군으로 묶을 수 있으며, 또한 OS별 군으로 나뉘게 된다.
- 최상위 인터페이스로 Component를 두고,
- 만들 추상 클래스로는 Component를 구현하는 Button, CheckBox, TextEdit이 있다.
- 위 추상 컴포넌트를 WindonwButton, MacButton 처럼 각 OS별로 구현해주어야 한다.
팩토리 메서드 패턴을 사용한다면, 다음과 같이 구성될 것이다.
- ComponentFactoryMethod(createOperation(String osType), createComponent(String osType))
- ButtonFactory
- WindowButton
- MacButton
- CheckBoxFactory
- WindowCheckBox
- MacCheckBox
- TextEditFactory
- WindowTextEdit
- MacTextEdit
Factory와 컴포넌트가 1:1이다. 때문에, 어느 OS인지는 각 팩토리 메서드 내에서 분기문을 통해 구분해야 한다.
- ButtonFactory
=> 새로운 OS가 추가된다면 각 메서드마다 있는 분기문 로직을 하나하나 수정해주어야 한다..
=> OCP 원칙에 위배된다!
추상 팩토리 패턴을 사용한다면, 다음과 같이 구성될 것이다.
- ComponentAbstractFactory (createButton(), createCheckBox(), createTextEdit())
- WindowFactory
- WindowButton
- WindowCheckBox
- WindowTextEdit
- MacFactory
- MacButton
- MacCheckBox
- MacTextEdit
=> 분기문이 필요없이 어떤 팩토리 객체를 생성하느냐에 따라 똑같은 메서드를 호출해도 다른 결과가 반환된다
=> 새로운 OS가 추가된다 하더라도, 기존 코드 수정 없이 새로운 OS에 대한 팩토리 클래스만 적절히 추가해주면 된다.
=> 단, 새로운 컴포넌트가 추가된다면 새로운ComponentAbstractFactory
인터페이스 내에createToolTop()
과 같은 새로운 메서드를 추가해주어야 하고, 이는 이를 구현하는 모든 클래스에 영향을 주게 되는 문제점이 생긴다.
- WindowFactory
어떠한 제품들에 대한 '군'을 묶어 생성해야 할때 추상 팩토리로 구성하는 것이 유지보수와 확장에 있어 더 유리하다
구현
Product
// Product A 제품군
interface AbstractProductA {
}
// Product A - 1
class ConcreteProductA1 implements AbstractProductA {
}
// Product A - 2
class ConcreteProductA2 implements AbstractProductA {
}
// Product B 제품군
interface AbstractProductB {
}
// Product B - 1
class ConcreteProductB1 implements AbstractProductB {
}
// Product B - 2
class ConcreteProductB2 implements AbstractProductB {
}
Factory
interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
// Product A1와 B1 제품군을 생산하는 공장군 1
class ConcreteFactory1 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
// Product A2와 B2 제품군을 생산하는 공장군 2
class ConcreteFactory2 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA2();
}
public AbstractProductB createProductB() {
return new ConcreteProductB2();
}
}
Main
class Client {
public static void main(String[] args) {
AbstractFactory factory = null;
// 1. 공장군 1을 가동시킨다.
factory = new ConcreteFactory1();
// 2. 공장군 1을 통해 제품군 A1를 생성하도록 한다 (클라이언트는 구체적인 구현은 모르고 인터페이스에 의존한다)
AbstractProductA product_A1 = factory.createProductA();
System.out.println(product_A1.getClass().getName()); // ConcreteProductA1
// 3. 공장군 2를 가동시킨다.
factory = new ConcreteFactory2();
// 4. 공장군 2를 통해 제품군 A2를 생성하도록 한다 (클라이언트는 구체적인 구현은 모르고 인터페이스에 의존한다)
AbstractProductA product_A2 = factory.createProductA();
System.out.println(product_A2.getClass().getName()); // ConcreteProductA2
}
}
=> 똑같은 createProductA()
메서드를 호출하지만 어떤 팩토리 객체이냐에 따라 반환되는 제품군이 다르게 된다
추가적으로 Factory 클래스들의 경우, 객체를 생성만 하면 되기 때문에 메모리 최적화를 위해 '싱글톤 패턴'을 적용해주는 것이 적절하다.
참고
[Inpa Dev - GOF](https://inpa.tistory.com/category/%EB%94%94%EC%9E%90%EC%9D%B8%20%ED%8C%A8%ED%84%B4/GOF?page=1)
[자바 디자인 패턴의 이해 - Gof Design Pattern
](https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4)
'ETC' 카테고리의 다른 글
[오브젝트 - 기초편] 영화 예매 도메인 예제 (3) | 2024.12.30 |
---|---|
[Design Pattern] Structural Pattern (0) | 2024.11.04 |
[Design Pattern] Behavioral Pattern(행동 패턴) - 2 (3) | 2024.11.03 |
[Design Pattern] Behavioral Pattern(행동 패턴) - 1 (1) | 2024.11.02 |
[CS] 문자 인코딩(Character Encoding) (1) | 2024.10.09 |