군만두의 IT 개발 일지

[Java] GoF 디자인 패턴 정리 - 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입, 싱글턴 본문

개발일지/패스트캠퍼스

[Java] GoF 디자인 패턴 정리 - 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입, 싱글턴

mandus 2024. 3. 14. 23:53

목차

    ⭐ 요약


    • 김강사님이 추천해주신 참고 사이트를 바탕으로 GoF 디자인 패턴을 학습한다.
    • 디자인 패턴 중 강사님이 특히 강조한 팩토리 메소드, 추상 팩토리, 빌더, 프로토타입, 싱글턴 5가지 생성 패턴을 중점적으로 정리한다.
    • 각 패턴의 개념, 장단점, Java 예시 코드를 함께 정리한다.

    ⭐ 참고 자료


    강사님이 추천해주신 사이트는 아래 두 곳이다.

    ⭐ 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

     

    이 글은 패스트캠퍼스백엔드 개발 캠프에서 공부한 내용을 작성한 것입니다.

     

    Comments