[JAVA] 디자인 패턴(1) | 생성 패턴 (Creational Patterns)

chaeyeong·2025년 3월 5일

자바 이론

목록 보기
8/9

객체 생성 디자인 패턴이란?
객체를 생성하는 과정에서 효율성과 유지보수를 고려하여 구조화된 방법을 제공하는 패턴


1. 생성자 패턴 (Constructor Pattern)

클래스를 인스턴스화 할 때 직접 생성자를 호출하여 객체를 생성하는 가장 기본적인 패턴이다.

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void displayInfo() {
        System.out.println("name: " + name + ", age: " + age);
    }

    public static void main(String[] args) {
        Person person = new Person("홍길동", 25);
        person.displayInfo();
    }
}

⭕️ 장점

  • 객체 생성이 직관적이고 간단하다.
  • 별도의 객체 생성 관리 클래스가 필요하지 않다.
  • 코드의 가독성이 높다.

❌ 단점

  • 객체의 상태를 변경할 수 있는 방법이 제한적이다.setter 메서드를 제공하지 않는 불변 객체의 경우 상태를 변경할 수 있는 방법이 없기 때문이다.
  • 생성자의 매개변수가 많아지면 코드의 가독성이 떨어진다.
  • 객체 생성 로직을 수정할 경우 기존 코드의 많은 부분을 수정해야 할 수 있다.


2. 정적 팩토리 메서드 패턴 (Static Factory Method Pattern)

클래스 내부에서 정적 메서드를 제공하여 객체를 생성하는 패턴이다.

public class Car {
    private String model;

    private Car(String model) {
        this.model = model;
    }

    public static Car createCar(String model) {
        return new Car(model);
    }

    public void showModel() {
        System.out.println("model: " + model);
    }

    public static void main(String[] args) {
        Car car = Car.createCar("Tesla");
        car.showModel();
    }
}

⭕️ 장점

  • 생성자의 한계를 극복하고 가독성을 높일 수 있다.
  • 메서드명을 의미 있게 지정할 수 있어서 코드를 이해하기 쉽다.
  • 캐싱을 통해 동일한 객체를 반환하는 등의 로직을 추가할 수 있다.

❌ 단점

  • 하위 클래스의 상속이 어렵다.
  • 생성자의 접근제어자가 private 여서 상속을 통한 다형성 구현이 제한될 수 있다.


3. 빌더 패턴 (Builder Pattern)

객체의 생성 과정을 단계별로 진행하고, 객체의 가독성을 높이는 패턴이다.

public class Computer {
    private String CPU;
    private String RAM;
    private String storage;

    private Computer(Builder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.storage = builder.storage;
    }

    public static class Builder {
        private String CPU;
        private String RAM;
        private String storage;

        public Builder setCPU(String CPU) {
            this.CPU = CPU;
            return this;
        }

        public Builder setRAM(String RAM) {
            this.RAM = RAM;
            return this;
        }

        public Builder setStorage(String storage) {
            this.storage = storage;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }

    public void showSpecs() {
        System.out.println("CPU: " + CPU + ", RAM: " + RAM + ", storage: " + storage);
    }

    public static void main(String[] args) {
        Computer computer = new Computer.Builder()
                .setCPU("Intel i7")
                .setRAM("16GB")
                .setStorage("512GB SSD")
                .build();
        computer.showSpecs();
    }
}

⭕️ 장점

  • 가독성이 뛰어나고 유지보수가 용이하다.
  • 선택적인 매개변수를 지원할 수 있다.
  • 불변 객체 생성에 적합하다.

❌ 단점

  • 코드가 길어질 수 있다.
  • 작은 객체를 만들 때엔 오버헤드가 발생할 수 있다.


4. 싱글톤 패턴 (singleton Pattern)

클래스의 인스턴스를 하나만 생성하고 이를 공유하는 패턴이다.

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public void showMessage() {
        System.out.println("싱글톤 객체입니다.");
    }

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        singleton1.showMessage();
        System.out.println(singleton1 == singleton2); // true
    }
}

⭕️ 장점

  • 전역 상태를 유지할 수 있다.
  • 메모리 낭비를 방지할 수 있다.
  • 인스턴스를 공유하므로 자원 낭비를 줄일 수가 있다.

❌ 단점

  • 멀티 스레드 환경에서는 동기화 처리가 필요하다.
  • 각 테스트 간에 싱글톤이 공유되면 한 테스트에서의 상태 변화가 다른 테스트에 영향을 줄 수 있으므로 테스트가 어려워질 수 있다.
  • 결합도가 높아져서 유지보수성이 떨어진다.


5. 팩토리 메서드 패턴 (Factory Mathod Pattern)

객체 생성을 서브 클래스에서 담당하도록 위임하는 패턴이다.
부모 클래스는 객체 생성 메서드를 제공하고, 자식 클래스는 실제 객체를 생성한다.

// 추상 Product 클래스
abstract class Product {
    abstract void use();
}

// 구체적인 Product 클래스들
class ConcreteProductA extends Product {
    @Override
    void use() {
        System.out.println("제품 A를 사용합니다.");
    }
}

class ConcreteProductB extends Product {
    @Override
    void use() {
        System.out.println("제품 B를 사용합니다.");
    }
}

// 추상 Creator 클래스: 객체 생성 책임을 정의
abstract class Creator {
    // 팩토리 메서드: 구체적인 객체 생성은 서브클래스에서 담당
    abstract Product factoryMethod();
}

// 구체적인 Creator 클래스들
class ConcreteCreatorA extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductA();
    }
}

class ConcreteCreatorB extends Creator {
    @Override
    Product factoryMethod() {
        return new ConcreteProductB();
    }
}

public class FactoryMethodExample {
    public static void main(String[] args) {
        Creator creatorA = new ConcreteCreatorA();
        Product productA = creatorA.factoryMethod();
        productA.use();

        Creator creatorB = new ConcreteCreatorB();
        Product productB = creatorB.factoryMethod();
        productB.use();
    }
}

⭕️ 장점

  • 객체 생성을 캡슐화 할 수 있다. 생성 로직을 서브 클래스에 숨겨서 클라이언트는 생성되는 객체의 구체적인 타입에 대해 신경 쓸 필요가 없다.
  • 새로운 클래스가 추가되어도 서브 클래스를 추가하기만 하면 기존 코드는 수정할 필요가 없다. 환경이나 요구사항에 따라 다른 구체적인 객체를 생성할 수 있도록 유연하게 설계할 수 있다.

❌ 단점

  • 제품마다 새로운 서브 클래스를 만들어야 해서 클래스 수가 늘어나며 구조가 복잡해질 수 있다.
  • 단순한 객체 생성에는 오히려 과도한 설계가 될 수 있다.


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

서로 관련된 객체 군을 생성할 수 있도록 도와주는 패턴이다.
일관된 스타일이나 기능을 유지하며 함께 사용될 수 있도록 한 팩토리에서 생성하도록 한다.

// 추상 제품군
interface Chair {
    void sit();
}

interface Sofa {
    void lieDown();
}

// 구체적인 제품: 서로 다른 스타일의 의자 구현
class ModernChair implements Chair {
    public void sit() {
        System.out.println("현대적인 의자에 앉았습니다.");
    }
}

class VictorianChair implements Chair {
    public void sit() {
        System.out.println("빅토리아 스타일 의자에 앉았습니다.");
    }
}

// 구체적인 제품: 서로 다른 스타일의 소파 구현
class ModernSofa implements Sofa {
    public void lieDown() {
        System.out.println("현대적인 소파에 누웠습니다.");
    }
}

class VictorianSofa implements Sofa {
    public void lieDown() {
        System.out.println("빅토리아 스타일 소파에 누웠습니다.");
    }
}

// 추상 팩토리 : 관련된 제품군(의자, 소파)을 생성하는 메서드 선언
interface FurnitureFactory {
    Chair createChair();
    Sofa createSofa();
}

// 구체적인 팩토리: 제품군 전체를 생성하는 방법 구현
class ModernFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new ModernChair();
    }

    public Sofa createSofa() {
        return new ModernSofa();
    }
}

class VictorianFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new VictorianChair();
    }

    public Sofa createSofa() {
        return new VictorianSofa();
    }
}

public class AbstractFactoryExample {
    public static void main(String[] args) {
        // 현대 스타일 가구 공장 사용
        FurnitureFactory modernFactory = new ModernFurnitureFactory();
        Chair modernChair = modernFactory.createChair();
        Sofa modernSofa = modernFactory.createSofa();
        modernChair.sit();
        modernSofa.lieDown();

        // 빅토리아 스타일 가구 공장 사용
        FurnitureFactory victorianFactory = new VictorianFurnitureFactory();
        Chair victorianChair = victorianFactory.createChair();
        Sofa victorianSofa = victorianFactory.createSofa();
        victorianChair.sit();
        victorianSofa.lieDown();
    }
}

⭕️ 장점

  • 같은 팩토리에서 생성된 제품들은 서로 조화를 이루도록 설계되므로 제품군 전체의 일관성을 유지할 수 있다.
  • 객체 생성 로직을 한 팩토리에서 관리하므로 클라이언트 코드가 구체적인 제품 구현에 의존하지 않는다.

❌ 단점

  • 멀티 스레드 환경에서는 동기화 처리가 필요하다.
  • 각 테스트 간에 싱글톤이 공유되면 한 테스트에서의 상태 변화가 다른 테스트에 영향을 줄 수 있으므로 테스트가 어려워질 수 있다.
  • 결합도가 높아져서 유지보수성이 떨어진다.


7. 프로토타입 패턴 (Prototype Pattern)

기존 객체를 복제(clone)하여 새로운 객체를 생성하는 패턴이다.

public class Prototype implements Cloneable {
    private String data;

    public Prototype(String data) {
        this.data = data;
    }

    public void showData() {
        System.out.println("Data: " + data);
    }

    @Override
    protected Prototype clone() throws CloneNotSupportedException {
        return (Prototype) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype original = new Prototype("Original");
        Prototype clone = original.clone();

        original.showData();
        clone.showData();
    }
}

⭕️ 장점

  • 객체를 빠르게 복제할 수 있어 성능이 향상된다.
  • 복잡한 객체 생성 시에 코드가 간결하다.
  • 객체의 구성을 그대로 유지할 수 있다.

❌ 단점

  • Cloneable 인터페이스를 구현해야 하며, 깊은 복사와 얕은 복사에 주의해야 한다.
  • 객체의 복사 과정에서 예상치 못한 문제가 발생할 수 있다. (예: 가변 객체의 참조 공유)
profile
그래도 해야지 어떡해

0개의 댓글