// 1. Define interface
interface Animal {
    void speak();
}

// 2. Implement concrete classes
class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("멍멍!");
    }
}

class Cat implements Animal {
    @Override
    public void speak() {
        System.out.println("야옹!");
    }
}

// 3. Factory class
class AnimalFactory {
    public static Animal createAnimal(String type) {
        if (type.equalsIgnoreCase("dog")) {
            return new Dog();
        } else if (type.equalsIgnoreCase("cat")) {
            return new Cat();
        }
        throw new IllegalArgumentException("Unknown animal type: " + type);
    }
}

// 4. Usage example
public class Main {
    public static void main(String[] args) {
        Animal dog = AnimalFactory.createAnimal("dog");
        dog.speak(); // Output: 멍멍!
        
        Animal cat = AnimalFactory.createAnimal("cat");
        cat.speak(); // Output: 야옹!
    }
}

팩토리 패턴은 객체 생성 과정이 복잡하거나, 객체 유형에 따라 다른 클래스가 생성될 필요가 있을 때 유용합니다. 몇 가지 대표적인 상황은 다음과 같습니다:

  1. 클라이언트 코드에서 객체 생성 로직을 숨기고 싶을 때 • 객체를 생성하는 방법이 복잡하거나 자주 변경되는 경우, 팩토리를 사용해 클라이언트가 그 세부 사항을 몰라도 되도록 만들 수 있습니다.
  2. 유연하게 다양한 객체를 생성해야 할 때 • 예를 들어, 같은 인터페이스를 구현한 여러 클래스가 있고, 조건에 따라 적합한 클래스의 인스턴스를 반환해야 할 때 팩토리 패턴을 사용하면 간단히 처리할 수 있습니다.
  3. 객체 생성 과정이 반복될 때 • 특정 객체를 만들기 위해 동일한 초기화 로직이 여러 곳에서 중복된다면, 팩토리 패턴으로 이 로직을 한 곳에 모아서 중복을 제거하고 코드의 일관성을 유지할 수 있습니다.
  4. 추후 확장성을 고려할 때 • 새로운 객체 유형이 추가될 가능성이 높다면, 팩토리 패턴을 도입해 객체 생성 과정을 캡슐화해 두는 것이 좋습니다. 이렇게 하면 클라이언트 코드를 수정하지 않고도 팩토리 클래스만 변경하여 새로운 객체를 생성할 수 있습니다.

요약하면, 팩토리 패턴은 “객체 생성 로직이 복잡하거나 유연성이 요구될 때” 사용하면 좋습니다.