Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- UXUI챌린지
- 부트캠프
- UXUI기초정복
- KDT
- OPENPATH
- Spring
- 백준
- 디자인강의
- 국비지원교육
- 백엔드 부트캠프
- baekjoon
- 국비지원
- 내일배움카드
- 티스토리챌린지
- 디자인챌린지
- Java
- API
- 오픈패스
- 디자인교육
- 국비지원취업
- UXUIPrimary
- 환급챌린지
- mysql
- Be
- 오픈챌린지
- 시스템설계
- 오블완
- 백엔드개발자
- 패스트캠퍼스
- JPA
Archives
- Today
- Total
군만두의 IT 개발 일지
[Java] GoF 디자인 패턴 정리 - 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입, 싱글턴 본문
목차
⭐ 요약
- 김강사님이 추천해주신 참고 사이트를 바탕으로 GoF 디자인 패턴을 학습한다.
- 디자인 패턴 중 강사님이 특히 강조한 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입, 싱글턴 5가지 생성 패턴을 중점적으로 정리한다.
- 각 패턴의 개념, 장단점, Java 예시 코드를 함께 정리한다.
⭐ 참고 자료
강사님이 추천해주신 사이트는 아래 두 곳이다.
- refactoring.guru - 디자인 패턴들: 각 패턴을 그림과 함께 직관적으로 설명하는 사이트이다. 한국어를 지원하며, 코드 예시도 다양한 언어로 제공한다.
- 한빛미디어 - 많이 쓰는 14가지 핵심 GoF 디자인 패턴의 종류: 실무에서 자주 쓰이는 핵심 GoF 패턴 14가지를 정리한 글이다.
⭐ GoF 디자인 패턴이란?
GoF(Gang of Four) 디자인 패턴은 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 네 명의 저자가 제안한 소프트웨어 설계에서 자주 발생하는 문제들을 해결하기 위한 23가지 설계 패턴이다. 패턴은 목적에 따라 아래 세 가지로 분류된다.
| 분류 | 설명 | 대표 패턴 |
| 생성 패턴 (Creational) | 객체 생성 방식을 다루는 패턴 | 싱글턴, 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입 |
| 구조 패턴 (Structural) | 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴 | 어댑터, 데코레이터, 프록시, 컴포지트 |
| 행동 패턴 (Behavioral) | 객체 간의 상호작용과 책임 분배를 다루는 패턴 | 옵저버, 전략, 커맨드, 이터레이터 |
강사님이 특히 강조한 패턴은 모두 생성 패턴(Creational Pattern)에 해당한다. 생성 패턴은 객체를 어떻게 생성할지를 캡슐화하여 코드의 유연성과 재사용성을 높이는 데 목적이 있다.
⭐ 핵심 패턴 정리
1. 팩토리 메소드 (Factory Method)
- 객체 생성을 서브클래스에 위임하는 패턴이다. 상위 클래스에서 객체를 생성하는 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 만들지는 서브클래스가 결정한다.
- 장점: 객체 생성 코드를 클라이언트와 분리하여 결합도를 낮출 수 있다. 새로운 타입의 객체를 추가할 때 기존 코드를 수정하지 않아도 된다(OCP 원칙).
- 단점: 새로운 제품 타입마다 서브클래스를 만들어야 하므로 클래스 수가 늘어날 수 있다.
// 공통 인터페이스
interface Animal {
void speak();
}
// 구체적인 제품 클래스
class Dog implements Animal {
@Override
public void speak() {
System.out.println("멍멍!");
}
}
class Cat implements Animal {
@Override
public void speak() {
System.out.println("야옹!");
}
}
// 팩토리 메소드를 가진 추상 클래스
abstract class AnimalFactory {
// 팩토리 메소드: 서브클래스가 객체 생성을 담당한다.
public abstract Animal createAnimal();
public void makeAnimalSpeak() {
Animal animal = createAnimal();
animal.speak();
}
}
// 구체적인 팩토리
class DogFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
class CatFactory extends AnimalFactory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
AnimalFactory factory = new DogFactory();
factory.makeAnimalSpeak(); // 멍멍!
factory = new CatFactory();
factory.makeAnimalSpeak(); // 야옹!
}
}
2. 추상 팩토리 (Abstract Factory)
- 서로 관련 있는 객체들의 집합을 생성하는 인터페이스를 제공하는 패턴이다. 팩토리 메소드 패턴을 한 단계 더 추상화한 것으로, 여러 종류의 팩토리 자체를 추상화한다.
- 장점: 관련된 객체들을 일관성 있게 생성할 수 있다. 제품군 전체를 교체할 때 클라이언트 코드를 수정하지 않아도 된다.
- 단점: 새로운 종류의 제품을 추가하려면 모든 팩토리 클래스를 수정해야 한다.
// 제품 인터페이스
interface Button {
void click();
}
interface TextField {
void input();
}
// Windows 계열 제품
class WindowsButton implements Button {
@Override
public void click() {
System.out.println("Windows 버튼 클릭");
}
}
class WindowsTextField implements TextField {
@Override
public void input() {
System.out.println("Windows 텍스트 입력");
}
}
// Mac 계열 제품
class MacButton implements Button {
@Override
public void click() {
System.out.println("Mac 버튼 클릭");
}
}
class MacTextField implements TextField {
@Override
public void input() {
System.out.println("Mac 텍스트 입력");
}
}
// 추상 팩토리: 관련 객체들의 생성 인터페이스를 정의한다.
interface GUIFactory {
Button createButton();
TextField createTextField();
}
// 구체적인 팩토리
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() { return new WindowsButton(); }
@Override
public TextField createTextField() { return new WindowsTextField(); }
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() { return new MacButton(); }
@Override
public TextField createTextField() { return new MacTextField(); }
}
// 사용 예시
public class Main {
public static void main(String[] args) {
GUIFactory factory = new WindowsFactory();
Button button = factory.createButton();
TextField textField = factory.createTextField();
button.click(); // Windows 버튼 클릭
textField.input(); // Windows 텍스트 입력
}
}
3. 빌더 (Builder)
- 복잡한 객체를 단계별로 생성하는 패턴이다. 생성자의 매개변수가 많아지는 문제를 해결하고, 객체 생성 과정과 표현 방식을 분리한다.
- 장점: 생성자에 매개변수가 많을 때 가독성이 좋아진다. 선택적 매개변수를 유연하게 처리할 수 있다. 불변 객체를 만들기에 적합하다.
- 단점: 빌더 클래스를 별도로 만들어야 하므로 코드 양이 늘어난다.
public class User {
// 필수 필드
private final String name;
private final String email;
// 선택 필드
private final String phone;
private final int age;
// 생성자를 private으로 막고 빌더를 통해서만 생성한다.
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.phone = builder.phone;
this.age = builder.age;
}
public static class Builder {
private final String name; // 필수
private final String email; // 필수
private String phone = ""; // 선택 (기본값)
private int age = 0; // 선택 (기본값)
public Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public User build() {
return new User(this);
}
}
@Override
public String toString() {
return "User{name=" + name + ", email=" + email
+ ", phone=" + phone + ", age=" + age + "}";
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
// 필수 값만 넣거나 선택 값을 자유롭게 추가할 수 있다.
User user = new User.Builder("홍길동", "hong@email.com")
.phone("010-1234-5678")
.age(30)
.build();
System.out.println(user);
}
}
4. 프로토타입 (Prototype)
- 기존 객체를 복사하여 새로운 객체를 만드는 패턴이다. 객체 생성 비용이 클 때 기존 인스턴스를 복제하여 사용한다. Java에서는
Cloneable인터페이스와clone()메소드를 활용한다. - 장점: 복잡한 객체를 매번 새로 생성하는 비용을 줄일 수 있다. 서브클래스 없이도 객체를 복제할 수 있다.
- 단점: 객체 내부에 참조 타입 필드가 있을 경우 얕은 복사(shallow copy)와 깊은 복사(deep copy)를 주의해서 구현해야 한다.
// Cloneable을 구현하여 clone()을 사용할 수 있다.
public class Book implements Cloneable {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
// 깊은 복사가 필요하면 이 메소드 안에서 참조 타입 필드도 별도로 복사해야 한다.
@Override
public Book clone() {
try {
return (Book) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void setTitle(String title) { this.title = title; }
@Override
public String toString() {
return "Book{title=" + title + ", author=" + author + "}";
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
Book original = new Book("클린 코드", "로버트 C. 마틴");
Book copy = original.clone(); // 기존 객체를 복사한다.
copy.setTitle("클린 아키텍처");
System.out.println(original); // Book{title=클린 코드, author=로버트 C. 마틴}
System.out.println(copy); // Book{title=클린 아키텍처, author=로버트 C. 마틴}
}
}
5. 싱글턴 (Singleton)
- 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴이다. 전역적으로 접근 가능한 단일 인스턴스를 제공한다.
- 장점: 인스턴스가 하나뿐이므로 메모리 낭비를 방지할 수 있다. DB 연결, 로그 관리 등 공유 자원을 다룰 때 유용하다.
- 단점: 멀티스레드 환경에서 동시에 접근하면 여러 인스턴스가 생성될 수 있으므로 동기화 처리가 필요하다. 전역 상태를 공유하므로 테스트가 어려워질 수 있다.
// LazyHolder 방식: 멀티스레드 환경에서도 안전하게 싱글턴을 구현한다.
public class DatabaseConnection {
private DatabaseConnection() {
// 외부에서 new로 생성하지 못하도록 생성자를 private으로 막는다.
}
// 내부 정적 클래스는 getInstance()가 호출될 때 JVM이 처음으로 로딩한다.
// 이때 JVM이 스레드 안전하게 초기화를 보장한다.
private static class LazyHolder {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
}
public static DatabaseConnection getInstance() {
return LazyHolder.INSTANCE;
}
public void connect() {
System.out.println("DB에 연결되었습니다.");
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
DatabaseConnection conn1 = DatabaseConnection.getInstance();
DatabaseConnection conn2 = DatabaseConnection.getInstance();
// 두 참조가 동일한 인스턴스를 가리키는지 확인한다.
System.out.println(conn1 == conn2); // true
conn1.connect();
}
}
⭐ 5가지 생성 패턴 비교
| 패턴 | 핵심 목적 | 주요 사용 상황 |
| 팩토리 메소드 | 객체 생성을 서브클래스에 위임함 | 생성할 객체 타입을 런타임에 결정할 때 |
| 추상 팩토리 | 관련 객체군을 일관성 있게 생성함 | OS, UI 라이브러리 등 제품군을 교체할 때 |
| 빌더 | 복잡한 객체를 단계별로 생성함 | 매개변수가 많고 선택 값이 있는 객체를 만들 때 |
| 프로토타입 | 기존 객체를 복사하여 새 객체를 만듦 | 객체 생성 비용이 크고 유사한 객체가 필요할 때 |
| 싱글턴 | 인스턴스가 하나만 생성되도록 보장함 | DB 연결, 로그 관리 등 공유 자원을 다룰 때 |
⭐ 후기
- 디자인 패턴을 처음 접할 때는 개념이 추상적이어서 이해가 어려웠다. 하지만 각 패턴을 직접 Java 코드로 작성해보면서 어떤 문제를 해결하기 위해 만들어진 패턴인지 이해할 수 있었다.
- 강사님이 특히 강조한 빌더 패턴과 싱글턴 패턴은 Spring 프레임워크 내부에서도 광범위하게 사용되는 패턴이다. Spring Bean이 기본적으로 싱글턴 스코프로 관리되고, Lombok의
@Builder가 빌더 패턴을 자동으로 구현해준다는 점에서, 이미 실무에서 매일 접하고 있던 패턴이었다는 점이 흥미로웠다.
⭐ 참고자료
1) refactoring.guru, "디자인 패턴들", https://refactoring.guru/ko/design-patterns
2) 한빛미디어, "[Design pattern] 많이 쓰는 14가지 핵심 GoF 디자인 패턴의 종류", https://m.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823
이 글은 패스트캠퍼스의 백엔드 개발 캠프에서 공부한 내용을 작성한 것입니다.
'개발일지 > 패스트캠퍼스' 카테고리의 다른 글
| 패스트캠퍼스 백엔드 개발 부트캠프 8기 Java 과제 PR 트러블슈팅 - Git 이메일 오류, 브랜치 충돌 해결 (0) | 2024.03.19 |
|---|---|
| 패스트캠퍼스 백엔드 개발 부트캠프 8기 그룹스터디 - 도서관 시스템 설계 (Java, JDBC, MySQL) (0) | 2024.03.15 |
| 패스트캠퍼스 백엔드 개발 부트캠프 8기 그룹 스터디 - Git 브랜치 전략과 협업 플로우 실습 (0) | 2024.03.14 |
| [후기] 패스트캠퍼스 백엔드 개발 부트캠프 8기 네트워킹 데이 (0) | 2024.03.14 |
| 패스트캠퍼스 백엔드 개발 부트캠프 8기 그룹스터디 - 교착 상태, DB 이상현상, 제네릭, HTTP & 암호화 (0) | 2024.03.10 |
Comments