dh_0e

[Design Pattern] Creational Design Patterns III (Abstract Factory, Builder, Prototype Pattern) 본문

Software Analysis & Design/Design Patterns

[Design Pattern] Creational Design Patterns III (Abstract Factory, Builder, Prototype Pattern)

dh_0e 2025. 11. 4. 10:59

Creational Pattern 종류

 

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

  • 연관 있는 여러 객체(제품) 군(family)의 생성을 추상화한 생성 패턴
    • 팩토리 메서드 패턴이 단일 객체 생성을 추상화하는 반면, 추상 팩토리 패턴은 관련 있는 여러 객체(제품)들의 일관된 생성을 추상화함
  • ex) {Chair, Sofa, CoffeeTable}에 대해 스타일 별로 각 제품을 일관되게 생성해야 함

  • 새로운 스타일의 제품 군이 추가되더라도 기존 코드를 변경하지 않고 추가할 수 있어야 합니다.
  • 제품별로 인터페이스를 추상화하고, 모든 추상 제품에 대한 생성 메서드를 가지는 팩토리로 추상화합니다.
  • 각 서브 팩토리에서는 스타일에 일관된 제품군이 생성될 수 있도록 생성 메서드를 구현합니다.

Factory Method Pattern vs Abstract Factory Pattern
Abstract Factory Pattern의 구조

 

ex) Window와 Mac OS의 Cross-platform GUI 예시

각각의 객체가 모두 Java 파일이라 보면 됨 (10개)

// 1. 버튼 인터페이스
interface Button {
    void click();
}

// 2. 체크박스 인터페이스
interface Checkbox {
    void check();
}

// --- Windows 스타일 부품들 ---
class WindowsButton implements Button {
    public void click() { System.out.println("Windows 버튼 클릭"); }
}

class WindowsCheckbox implements Checkbox {
    public void check() { System.out.println("Windows 체크박스 체크"); }
}

// --- Mac 스타일 부품들 ---
class MacButton implements Button {
    public void click() { System.out.println("Mac 버튼 클릭"); }
}

class MacCheckbox implements Checkbox {
    public void check() { System.out.println("Mac 체크박스 체크"); }
}

interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Windows 전용 공장 (윈도우 부품만 나옴)
class WindowsFactory implements GUIFactory {
    public Button createButton() { return new WindowsButton(); }
    public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

// Mac 전용 공장 (맥 부품만 나옴)
class MacFactory implements GUIFactory {
    public Button createButton() { return new MacButton(); }
    public Checkbox createCheckbox() { return new MacCheckbox(); }
}

class Application {
    private Button button;
    private Checkbox checkbox;

    // 생성자에서 공장을 받아서 부품을 조립
    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.click();
        checkbox.check();
    }
}

public class Main {
    public static void main(String[] args) {
        // 설정에 따라 공장만 갈아끼우면 전체 테마가 바뀜!
        GUIFactory factory;
        
        String osName = "mac"; // 만약 이 값이 "windows"라면?

        if (osName.equals("mac")) {
            factory = new MacFactory();
        } else {
            factory = new WindowsFactory();
        }

        // 앱 실행 (앱은 이게 윈도우인지 맥인지 모름, 그냥 인터페이스만 믿고 씀)
        Application app = new Application(factory);
        app.paint();
    }
}

추상 팩토리 패턴 장단점

  • 장점
    • 객체를 생성하는 코드를 분리하여 클라이언트 코드와 결합도를 낮출 수 있음
    • 제품 군(family)을 쉽게 대체할 수 있음
    • 단일 책임 원칙(SRP)및 개방-폐쇄 원칙(OCP)을 준수함
      • ex) 위 Cross-platform 예시에서 Linux에 관한 Factory를 추가해도 코드를 추가하지 않음

  • 단점
    • 제품, 팩토리들을 모두 구현해주어야 하므로 코드 구조 복잡성이 높음 (팩토리 메서드 패턴과 동일)
    • 새로운 제품 추가 시 모든 팩토리 구현 로직에 새로운 생성 함수가 추가되어야 함
구분 팩토리 메서드 추상 팩토리
목적 생성 책임을 서브클래스의 메서드로 위임 관련 제품군을 한꺼번에 일관되게 생성
범위 단일 제품 계열의 한 타입 여러 제품 타입으로 이뤄진 제품군
구조 Creator + factoryMethod() 1개 Product 인터페이스 AbstractFactory + 여러 makeXxx() + 여러 Product 인터페이스
확장 새 제품 타입마다 Creator 서브클래스 추가 새 제품군마다 ConcreteFactory와 구현 세트 추가
교체 단위 제품 타입 단위 교체 제품군(테마/플랫폼) 단위 교체
쓰는 때 생성 방법을 하위가 결정해야 할 때 버튼·체크박스처럼 함께 맞춰 바꿔야 할 타입들이 있을 때
  • 팩토리 메서드: 공장 → 제품(한 계열)
  • 추상 팩토리: 공장 → 연관된 제품들(제품 패밀리)

 

Builder Pattern (빌더 패턴)

  • 복잡한 객체 생성 과정과 표현 방법을 분리하여 클라이언트가 다양한 구성을 조합하여 객체를 생성할 수 있도록 하는 생성 패턴
  • 객체 생성 및 초기화 시 멤버 변수가 많을 경우, 모든 멤버 변수를 생성자에 넣는 것이 비효율적일 수 있음
    • 특정 개체 생성 시 모든 멤버 변수의 값이 쓰이지 않는다면 더욱 그럼

  • 클래스 내에서 객체 생성에 관련된 코드를 빼내서 Builder라는 별도의 객체로 옮기고, Builder를 이용하여 값을 설정할 수 있게 하는 방법

모두 함수로 표현
Builder Pattern

 

ex) Car & CarManual의 Builder pattern

Builder 재사용

 

Builder Pattern 구현

Builder & HouseBuilder
Director & Client

 

Builder Pattern 장단점

  • 장점
    • 순차적 조립: 구성하기 복잡한 객체를 순차적으로 만들 수 있음
    • 생성 과정 추상화: 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있음
    • 재사용성: 동일한 프로세스를 통해 구성에 따라 다른 객체를 만들 수 있음
    • 신뢰성: 불완전한 객체를 사용하지 못하도록 방지 할 수 있음

  • 단점
    • 선행적으로 빌더 객체를 먼저 만들어야 원하는 객체를 만들 수 있음
    • 객체 생성을 위한 코드보다 구조적으로 복잡해질 수 있음
      • 간단한 객체는 생성자를 통해서 만드는 것이 더 좋음

 

Prototype Pattern (프로토타입 패턴)

  • 기존의 객체를 복제(clone)하여 새로운 객체를 만드는 생성 패턴
  • 클래스에 의존하지 않으면서 기존 객체 복사 가능
  • 원형(prototypical) 인스턴스를 사용하여 생성할 객체의 종류를 명시, 견본을 복사해서 새로운 객체 생성
  • 객체 생성/복사가 까다로운 상황(ex. private/protected 멤버 변수 때문에 클래스 외부에서 복사 불가 / 객체 생성에 많은 리소스가 필요한 경우)에 유용

Java에선 Cloneable이라는 Prototype Pattern을 위한 인터페이스가 있음

Cloneable 인터페이스 in Java

  • 자바에서 $Objects.clone()$ 메소드는 인스턴스 객체의 복제를 위한 메소드
  • 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 레퍼런스를 반환
  • clone() 메소드를 사용하려면 Cloneable 인터페이스를 implements 하고 clone() 메소드를 오버라이딩 해주어야 함 (Prototype 인터페이스 역할)

※ clone()을 꼭 Overriding 해줘야 함

  • Shallow copy(얕은 복사): 객체의 필드 값만 복사 (포인터 값 복사)

  • Deep cop(깊은 복사): 독립적인 복제 객체로 모든 값을 복사 (일반 복사)

 

ex) Prototype pattern with shallow copy

firstTeam의 멤버 James를 Gale로 바꾸니 얕은 복사를 한 copyTeam의 멤버도 바뀜

  • 만약 deep clone이 되게 복사를 하고 싶으면 clone() 메소드에서 deep copy 될 수 있도록 변경해 주면 됨

깊은 복사를 한 copyTeam의 James는 안 바뀜

 

Prototype pattern의 장단점

  • 장점
    • 간단한 과정: 객체를 생성하는 복잡한 과정을 피할 수 있음 (생성자 호출 대신 복제)
    • 빠른 속도: 객체를 복제하는 것이 객체를 새로 생성하는 것보다 일반적으로 더 빠름
    • 런타임에 객체의 타입을 동적으로 변경하거나 새로운 객체 유형을 추가하는 데 유용함
      • new Goblin()이 여러 개 박혀있는 코드에서 Dragon으로 소환수를 바꾸고자 할 때, 원래는 코드를 모두 수정해야 함
      • >> prototype으로 런타임 도중에 쉽게 바꿀 수 있음
Monster prototype = new Goblin();  // 샘플 하나 들고 있음

Monster spawn() {
    return prototype.clone();      // 샘플을 복사해서 소환
}

prototype = new Dragon();   // 런타임에 바꿔치기

register("slime", new Slime());   // 새 프로토타입 추가
Monster m = create("slime");      // 등록된 샘플을 복사해서 생성
  • 단점
    • 복제 과정 혼잡: 객체가 복잡한 구조를 가지고 있거나 객체 간에 서로 참조가 있을 때, 객체 복제 과정(특히 깊은 복사 구현)이 복잡해질 수 있음
    • 메모리 사용량 문제: 프로토타입 패턴은 객체를 복제하므로 남발 시 메모리 사용량이 늘어날 수 있음
    • 객체 복제로 인해 객체의 상태 관리가 어려워질 수 있음