본문 바로가기
Programming/Design Patterns

디자인 패턴 1 : 생성패턴 - 팩토리 메서드, 추상팩토리, 빌더

by Mandy's 2025. 10. 21.

https://refactoring.guru/ko/design-patterns/catalog

 

디자인 패턴 목록

 

refactoring.guru


생성패턴: 팩토리 메서드, 추상팩토리, 빌더

생성: 객체를 어떻게 생성을 할 것인가

구조: 어떻게 조합을 하는가

행동: 객체들간의 책임을 어떻게 분배할 것인가 (결합도)

생성패턴

 

팩토리 메서드 패턴
  • 객체의 생성을 여러 가지로 다르게 생성을 하고 싶은데 매번 타입을 받을 때마다 바꿔줄 수가 없으니 특정 인터페이스를 생성하고 이를 구현하는 구현체에 따라서 내가 생성해서 가져온다는 것.
  • 스프링내부에서 실제로 사용되고 있는 패턴 - bean
package io.study.creational;

public class FactoryMethodPattern {
  public static void main(String[] args) {
    // 클라이언트는 구체적인 io.study.creational.Latte, io.study.creational.Americano 클래스를 몰라도 됨
    CoffeeFactory myFactory = new LatteFactory();
    myFactory.takeOrder();

    CoffeeFactory otherFactory = new AmericanoFactory();
    otherFactory.takeOrder();
  }
}

// 1. Product (인터페이스)
interface Coffee {
  void brew();
}

// 2. ConcreteProduct (구체적 제품)
class Latte implements Coffee {
  public void brew() { System.out.println("부드러운 라떼를 만듭니다."); }
}
class Americano implements Coffee {
  public void brew() { System.out.println("진한 아메리카노를 만듭니다."); }
}

// 3. Creator (추상 클래스)
abstract class CoffeeFactory {
  // 팩토리 메서드 (자식이 구현해야 함)
  public abstract Coffee createCoffee();

  // 팩토리 메서드를 사용하는 로직
  public void takeOrder() {
    Coffee coffee = createCoffee(); // 어떤 커피가 나올지 모름!
    coffee.brew();
  }
}

// 4. ConcreteCreator (구체적 생성자)
class LatteFactory extends CoffeeFactory {
  public Coffee createCoffee() {
    return new Latte(); // 라떼를 생성
  }
}
class AmericanoFactory extends CoffeeFactory {
  public Coffee createCoffee() {
    return new Americano(); // 아메리카노를 생성
  }
}

 

 

추상 팩토리
  • 최소한의 집합체 (이거는 있어야해!)
  • id, password, driver, port, db name → 인터페이스로 미리 선언을 해둠
    • 어떤 db를 가져와도 인터페이스 구현체에 따라서 mysql, postsql이든 뭐든 사용할 수 있다.
  • 구현체가 여러가지가 있다
package io.study.creational;

public class AbstractFactoryPattern {

  public static void main(String[] args) {
    // 클라이언트는 처음에 "어떤 스타일"의 팩토리를 쓸지만 결정하면 됨
    FurnitureFactory factory = new ModernFactory();
    // (만약 VintageFactory로 바꾸면, 아래 코드는 수정할 필요 없이 제품 스타일이 전부 바뀜)

    // 클라이언트는 ModernChair, ModernSofa의 존재를 몰라도 됨
    Chair chair = factory.createChair();
    Sofa sofa = factory.createSofa();

    chair.sitOn();
    sofa.lieOn();
  }
}

// 1. AbstractProduct (제품 인터페이스)
interface Chair { void sitOn(); }
interface Sofa { void lieOn(); }

// 2. ConcreteProduct (구체적인 제품)
class ModernChair implements Chair {
  public void sitOn() { System.out.println("모던한 의자에 앉습니다."); }
}
class VintageChair implements Chair {
  public void sitOn() { System.out.println("빈티지 의자에 앉습니다."); }
}
class ModernSofa implements Sofa {
  public void lieOn() { System.out.println("모던한 소파에 눕습니다."); }
}
class VintageSofa implements Sofa {
  public void lieOn() { System.out.println("빈티지 소파에 눕습니다."); }
}

// 3. AbstractFactory (추상 팩토리)
// "의자"와 "소파"를 만들어야 한다는 것을 정의
interface FurnitureFactory {
  Chair createChair();
  Sofa createSofa();
}

// 4. ConcreteFactory (구체적인 팩토리)
// "모던" 팩토리는 모던 제품만 생성
class ModernFactory implements FurnitureFactory {
  public Chair createChair() {
    return new ModernChair();
  }
  public Sofa createSofa() {
    return new ModernSofa();
  }
}
// "빈티지" 팩토리는 빈티지 제품만 생성
class VintageFactory implements FurnitureFactory {
  public Chair createChair() {
    return new VintageChair();
  }
  public Sofa createSofa() {
    return new VintageSofa();
  }
}

 

 

빌더
  • 객체 생성할 때 가독성을 좋게 하기 위해서 사용하는 것
  • user - setage, setname을 하기 귀찮으니까 생성자에 따라서 입력을 하는 것이 아니라 age, name은 몇 넣고 해서 가독성을 높임 ..
  • 스프링에서 롬복으로 해당 작업을 진행함. - 장고는 어떻게 하나?
package io.study.creational;

public class BuilderPattern {

  public static void main(String[] args) {
    // 빌더 패턴 사용
    User user1 = new User.UserBuilder("id123", "Alice")
        .age(30)
        .phone("010-1234-5678")
        .build();

    // 선택 항목(age)을 빼고 싶으면 메서드 호출만 안 하면 됨
    User user2 = new User.UserBuilder("id456", "Bob")
        .phone("010-9999-8888")
        .build();

    System.out.println( user1.getName() );
    System.out.println( user2.getName() );
  }
}

// 1. Product (제품) - User 클래스
class User {
  // 필드를 final로 선언하여 불변성 확보
  private final String id;   // 필수
  private final String name; // 필수
  private final int age;     // 선택
  private final String phone; // 선택

  // 생성자를 private으로! 오직 Builder만 호출 가능
  private User(UserBuilder builder) {
    this.id = builder.id;
    this.name = builder.name;
    this.age = builder.age;
    this.phone = builder.phone;
  }

  public String getName() {
    return name;
  }

  // 2. Builder (빌더) - static inner class
  public static class UserBuilder {
    // Product의 필드와 동일한 필드를 가짐
    private final String id;   // 필수
    private final String name; // 필수
    private int age;     // 선택 (기본값 0)
    private String phone; // 선택 (기본값 null)

    // 필수 필드는 Builder의 생성자에서 받음
    public UserBuilder(String id, String name) {
      this.id = id;
      this.name = name;
    }

    // 선택 필드는 메서드 체이닝으로 설정 (return this;)
    public UserBuilder age(int age) {
      this.age = age;
      return this; // 자신을 반환하여 체이닝 가능
    }

    public UserBuilder phone(String phone) {
      this.phone = phone;
      return this;
    }

    // 3. build() 메서드: 최종 객체를 생성
    public User build() {
      // 여기서 유효성 검사(validation)도 가능
      // 예: if (age < 0) throw new IllegalStateException(...)

      // private인 User 생성자를 호출
      return new User(this);
    }
  }
}