Search
Duplicate
📒

[Java Study] 01-1. 정적 팩토리 메소드, 빌더 패턴, 싱글톤

상태
완료
수업
Java Study
주제
연관 노트
3 more properties
참고

정적 팩토리 메소드

NOTE
정적 팩토리 메소드는 인스턴스 생성을 담당하는 메소드를 의미하며, 직접적으로 인스턴스를 생성하는는 대신 메소드 내부에서 인스턴스를 생성하고 반환하는 방식으로 동작합니다.
public class Boolean { // true, false 생성(중복 생성 방지) public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); private final boolean value; // 생성자 private Boolean(boolean value) { this.value = value; } // 정적 팩토리 메소드 public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } }
Java
복사
valueOf를 사용해서 기존의 객체를 반환한다.
정적 팩토리 메소드방식은 객체 생성 과정에서 이름을 부여하여 코드의 이해를 돕고, 다양한 방식으로 객체를 생성할 수 있는 유연성을 제공합니다.
필요한 경우, 정적 팩토리 메소드는 새로운 객체를 생성하지 않고 기존의 객체를 재사용하여 메모리 효율성을 높일 수 있습니다.
public class Book { // 객체 상속을 위해 생성자 public public Book() {} // 정적 팩토리 메소드로 하위타입 제공 public static Ebook getEbook(){ return new Ebook(); } public static AudioBook getAudioBook(){ return new AudioBook(); } public static PrintBook getPrintBook(){ return new PrintBook(); } // 정적 팩토리 메소드로 인자값에 따른 타입 제공 public static Book getBook(int pages){ if (pages > 1000) { return new Ebook(); } if (pages > 500) { return new AudioBook(); } return new PrintBook(); } }
Java
복사
정적 팩토리 메서드로 하위 타입을 반환하거나, 인자에 따라 반환타입을 변화시킬 수 있다.
정적 팩토리 메서드는 하위 타입을 반환하거나, 인자에 따라 반환타입을 변화시키는 등
하지만 정적 팩토리만으로 객체의 생성을 만들기 위해 생성자를 private로 만들어 버리면 상속을 할 수 없으며, 정적 팩토리 메소드는 개발자가 찾이 어렵다는 단점이 있습니다.

정적 팩토리 메서드

NOTE
public class Book { private Book() {} public static Ebook getEbook(){ return new Ebook(); } public static AudioBook getAudioBook(){ return new AudioBook(); } public static PrintBook getPrintBook(){ return new PrintBook(); } public static Book getBook(int pages){ if (pages > 1000) { return new Ebook(); } if (pages > 500) { return new AudioBook(); } return new PrintBook(); } }
Java
복사
class Books { public static EBook getEBook() { return new EBook(); } public static AudioBook getAudioBook() { return new AudioBook(); } public static PrintedBook getPrintedBook() { return new PrintedBook(); } }
Java
복사
Books 클래스에서 하위 클래스를 반환한다.
EBook book1 = Books.getEBook(); AudioBook book2 = Books.getAudioBook(); PrintedBook book3 = Books.getPrintedBook();
Java
복사
예시 코드
정적 팩토리 메소드는 하위 자료형 객체를 반환할 수 있습니다.
클래스의 다형성의 특징을 응용한 정적 팩토리 메서드의 특징입니다. 메서드 호출을 통해 얻을 객체의 인스턴스를 자유롭게 선택할 수 있습니다.
class Books { // 인자에 따라 다른 객체를 반환하도록 분기할 수 있습니다. public static Book getBook(int pages) { if (pages > 1000) { return new EBook(); } if (pages > 500) { return new AudioBook(); } return new PrintedBook(); } }
Java
복사
pages에 따른 다른 타입의 책 반환
Book book4 = Books.getBook(1200); // EBook 반환
Java
복사
예시 코드

정적 팩토리 메서드의 명명 방식

NOTE
정적 팩토리 메서드는 프로그래머가 찾기 어렵다는 단점을 극복하기 위해 암묵적인 명명 방식을 사용하고 있습니다.
메서드 이름
설명
예시
from
하나의 매개변수를 받아 해당 타입의 인스턴스로 변환하는 메서드입니다.
Date.from(Instant instant)
of
여러 매개변수를 집계하여 단일 객체를 반환하는 메서드입니다.
EnumSet.of(E first, E... others)
valueOf
주어진 매개변수로부터 인스턴스를 반환하는 메서드로, 주로 기본 타입 객체를 반환할 때 사용됩니다.
BigInteger.valueOf(int val)
instance, getInstance
매개변수에 따라 인스턴스를 반환하지만, 동일한 인스턴스가 항상 보장되는 것은 아닙니다.
Calendar.getInstance()
create, newInstance
매번 호출될 때마다 새로운 인스턴스를 생성하여 반환합니다.
Array.newInstance(classObject, arrayLen)
getType
생성하는 인스턴스의 종류가 더 구체적이거나 특수한 경우에 사용됩니다.
DocumentBuilderFactory.newDocumentBuilder()
newType
새로운 인스턴스를 생성할 때 좀 더 명확하게 타입을 지정하는 데 사용됩니다.
Files.newBufferedReader(Path path)
getTYPE
리턴되는 객체의 타입을 명확하게 알려주기 위해 사용됩니다.
Collections.emptyList()
// from: 매개 변수 하나를 받아서 반환한다. public static Book from(Book book) { return new Book(book.id, book.title, book.author); } // of: 매개 변수 여러개를 받아서 반환한다. public static Book of(String title, String author) { String id = UUID.randomUUID().toString(); return new Book(id, title, author); } // valueOf: from, of의 더 상세한 버전이다. (주로 래퍼 클래스에서 볼 수 있다.) public static Book valueOf(String bookInfo) { String[] parts = bookInfo.split(","); String id = UUID.randomUUID().toString(); return new Book(id, parts[0], parts[1]); } // getInstance: 매개 변수로 명시한 인스턴스를 반환, 같은 인스턴스를 보장하지는 않는다. public static Book getInstance(String title, String author) { return new Book(UUID.randomUUID().toString(), title, author); }
Java
복사
매개변수 명명방식

빌더 패턴

NOTE
빌더 패턴을 사용하면 클라이언트 코드에서 필요한 매개변수를 순차적으로 구성하고 가독성을 증가시킬 수 있습니다!
빌더 패턴은 JavaBeans 패턴의 장점인 가독성을 가지며, 끼워 넣기 생성자 패턴(telescoping constructor pattern)의 장점인 객체의 불변성을 가집니다. 빌더 패턴을 사용하는 클라이언트는 Builder 객체를 통해 객체를 생성합니다.
클라이언트는 빌더 객체를 처음 생성할 때 필수 파라미터를 전달해야 합니다.
클라이언트가 상황에 따라 필요한 옵션 파라미터를 주입하려면 빌더 객체의 setter와 같은 메서드를 호출하면 됩니다.
마지막으로, 클라이언트는 객체를 얻기 위해 빌드 완료 메서드를 호출합니다.

빌더 패턴 만들기

NOTE
public class Book { private final String title; // 필수 private final String author; // 필수 private final int publicationYear; // 선택적 private final String publisher; // 선택적 private final int pageCount; // 선택적 private Book(Builder builder) { this.title = builder.title; this.author = builder.author; this.publicationYear = builder.publicationYear; this.publisher = builder.publisher; this.pageCount = builder.pageCount; } public static class Builder { // 필수 매개변수 private final String title; private final String author; // 선택적 매개변수 - 기본값으로 초기화 private int publicationYear = 0; private String publisher = ""; private int pageCount = 0; // 1. 빌더 객체 사용을 위해 필수 매개변수 입력 public Builder(String title, String author) { this.title = title; this.author = author; } // 2. 선택 매개변수는 선택적으로 호출되도록 한다. public Builder publicationYear(int val) { publicationYear = val; return this; } public Builder publisher(String val) { publisher = val; return this; } public Builder pageCount(int val) { pageCount = val; return this; } // 3. 작업이 완료되면 완성 메서드를 호출한다. public Book build() { return new Book(this); } } // ... }
Java
복사
Builder 코드
Book book = new Book.Builder("Effective Java", "Joshua Bloch") .publicationYear(2008) .publisher("Addison-Wesley") .pageCount(384) .build();
Java
복사
사용 코드
빌더 패턴은 명명된 선택적 매개변수를 흉내낸것으로 쓰고 읽기가 좋습니다.
불변: 어떠한 변경도 허용하지 않는 다는 의미로, 가변객체와 구분하는 용도로 쓰입니다.
불변식: 프로그램이 실행되는 동안, 혹은 정해진 기간동안 반드시 만족해야하는 조건을 말합니다.

빌더 패턴 - 게층적 빌더

NOTE
public abstract class Book { public enum Genre { FICTION, NONFICTION, SCIENCE, HISTORY, FANTASY } final Set<Genre> genres; abstract static class Builder<T extends Builder<T>> { EnumSet<Genre> genres = EnumSet.noneOf(Genre.class); // 장르를 저장하는 가변 EnumSet입니다. // 장르를 추가하는 메서드입니다. // 자기 자신(T)을 반환하여 연쇄적 호출을 가능하게 합니다. public T addGenre(Genre genre) { genres.add(Objects.requireNonNull(genre)); // null 장르를 방지합니다. return self(); // 구현 클래스에서 이 메서드를 오버라이드 해야 합니다. } abstract Book build(); // 구체적인 Book 객체를 생성하여 반환합니다. protected abstract T self(); // 하위 클래스에서 자기 자신의 인스턴스를 반환합니다. } Book(Builder<?> builder) { genres = builder.genres.clone(); // Builder에서 설정한 장르를 복사합니다. } }
Java
복사
서브타입(장르) 설정
class FictionBook extends Book { private final String title; private final String author; static class Builder extends Book.Builder<Builder> { private final String title; private final String author; public Builder(String title, String author) { this.title = title; this.author = author; } // 실제 객체 반환 @Override FictionBook build() { return new FictionBook(this); } // self(자기 자신) 반환 @Override protected Builder self() { return this; } } private FictionBook(Builder builder) { super(builder); title = builder.title; author = builder.author; } // ... }
Java
복사
Book 구현체
FictionBook book = new FictionBook.Builder("The Hobbit", "J.R.R. Tolkien") .addGenre(Book.Genre.FICTION) .addGenre(Book.Genre.FANTASY) .build();
Java
복사
실제 사용
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋습니다.
추상 클래스에는 추상 빌더를, 구체 클래스에는 구체 빌더를 작성
하위 클래스의 build메서드는 구체 하위 클래스를 반환하도록 선언합니다.
생성자와 달리 가변 인수 매개변수를 여러개 사용하는 것이 가능합니다.

Private 생성자나 열거 타입으로 싱글턴 생성

NOTE
정적 멤버만 담은 유틸리티의 클래스 경우 인스턴스로 만들어지면 안된다. 그러므로 생성자를 private로 작성해서 인스턴스화를 막아야 한다!
public class Singleton { // 싱글톤 static public static final Elvis INSTANCE = new Elvis(); private Singleton() {} // 인스턴스 반환 public static Singleton getInstance() { return INSTANCE; } }
Java
복사
1. public static final 필드
해당 클래스가 싱글턴임이 API에 명확히 드러나며 간결합니다.
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} // 정적 팩토리 메소드 public static Singleton getInstance() { return INSTANCE; } }
Java
복사
2. 정적팩토리
정적 팩토리를 제네릭 싱글턴 팩토리로 변경할 수 있습니다. (아이템 30)
정적 팩터리의 메서드 참조를 공급자로 사용할 수 있습니다.
Singleton::getInstance → Supplier<Printer>
API를 변경하지 않고도 싱글턴이 아닌 것으로 변경할 수 있습니다.
public enum SingletonEnum { INSTANCE; public void displayMessage() { System.out.println("This is a singleton class using enum."); } }
Java
복사
3. enum (권장)
대다수의 경우, 원소가 하나만 있는 열거 타입이 싱글턴을 생성하는 가장 효율적인 방법입니다.
그러나, 만약 생성하려는 싱글턴이 Enum 이외의 클래스를 상속해야 하는 경우에는 이 방법을 사용할 수 없습니다.