Factory 패턴은 객체 생성을 캡슐화하여 유연성과 확장성을 제공하는 디자인 패턴입니다. 이 패턴은 클라이언트에게 직접 객체 생성을 맡기지 않고, 팩토리 메서드를 통해 객체를 생성하고 반환하는 방식을 사용합니다.
Factory 패턴의 개요
Factory 패턴은 클라이언트가 객체를 생성하는 대신, 팩토리 메서드를 호출하여 객체를 생성하고 반환합니다. 이를 통해 클라이언트는 객체 생성과 관련된 복잡한 로직을 알 필요 없이, 팩토리 메서드를 통해 쉽게 객체를 생성할 수 있습니다.
// 동물 인터페이스
public interface Animal {
void makeSound();
}
// 고양이 클래스
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("야옹");
}
}
// 개 클래스
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍");
}
}
// 새 클래스
public class Bird implements Animal {
@Override
public void makeSound() {
System.out.println("짹짹");
}
}
// Animal을 생성하는 AnimalFactory 클래스
public class AnimalFactory {
public static Animal createAnimal(String type) {
if (type.equalsIgnoreCase("고양이")) {
return new Cat();
} else if (type.equalsIgnoreCase("개")) {
return new Dog();
} else if (type.equalsIgnoreCase("새")) {
return new Bird();
}
return null;
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Animal cat = AnimalFactory.createAnimal("고양이");
cat.makeSound(); // 출력: "야옹"
Animal dog = AnimalFactory.createAnimal("개");
dog.makeSound(); // 출력: "멍멍"
Animal bird = AnimalFactory.createAnimal("새");
bird.makeSound(); // 출력: "짹짹"
}
}
Factory 패턴의 장점
- 객체 생성 로직을 캡슐화하여 코드의 가독성과 유지보수성을 향상시킵니다.
- 새로운 객체를 추가하거나 기존 객체를 변경할 때, Factory 클래스의 팩토리 메서드만 수정하면 되므로 확장성이 좋습니다.
- 클라이언트는 객체 생성에 대한 결정을 팩토리 메서드에 위임함으로써 의존성을 낮출 수 있습니다.
Factory 패턴을 잘못 사용하는 경우
Factory 패턴을 잘못 사용하면 코드가 복잡해질 수 있습니다. 과도한 팩토리 메서드의 사용이나 복잡한 조건문으로 인해 코드의 가독성과 유지보수성이 저하될 수 있습니다. 따라서, 적절한 상황과 용도에서 Factory 패턴을 사용해야 합니다.
과도한 팩토리 메서드 사용으로 가독성과 유지보수성이 저하되는 예제 코드를 보여드리겠습니다:
// 동물 인터페이스
public interface Animal {
void makeSound();
}
// 고양이 클래스
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("야옹");
}
}
// 개 클래스
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍");
}
}
// 새 클래스
public class Bird implements Animal {
@Override
public void makeSound() {
System.out.println("짹짹");
}
}
// Animal을 생성하는 AnimalFactory 클래스
public class AnimalFactory {
public static Animal createAnimal(String type) {
if (type.equalsIgnoreCase("고양이")) {
return new Cat();
} else if (type.equalsIgnoreCase("개")) {
return new Dog();
} else if (type.equalsIgnoreCase("새")) {
return new Bird();
} else if (type.equalsIgnoreCase("토끼")) {
return new Rabbit();
} else if (type.equalsIgnoreCase("뱀")) {
return new Snake();
}
return null;
}
}
// 토끼 클래스
public class Rabbit implements Animal {
@Override
public void makeSound() {
System.out.println("깡총깡총");
}
}
// 뱀 클래스
public class Snake implements Animal {
@Override
public void makeSound() {
System.out.println("실실");
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Animal cat = AnimalFactory.createAnimal("고양이");
cat.makeSound(); // 출력: "야옹"
Animal dog = AnimalFactory.createAnimal("개");
dog.makeSound(); // 출력: "멍멍"
Animal bird = AnimalFactory.createAnimal("새");
bird.makeSound(); // 출력: "짹짹"
Animal rabbit = AnimalFactory.createAnimal("토끼");
rabbit.makeSound(); // 출력: "깡총깡총"
Animal snake = AnimalFactory.createAnimal("뱀");
snake.makeSound(); // 출력: "실실"
}
}
위의 예제에서는 Factory 패턴을 사용하여 객체를 생성하고 있지만, 팩토리 메서드의 분기문이 많아져 가독성이 저하됩니다. 새로운 동물을 추가할 때마다 AnimalFactory 클래스의 팩토리 메서드를 수정해야 하는 번거로움이 있습니다. 이러한 상황에서는 팩토리 메서드의 사용을 줄이고, 추상 팩토리 패턴을 고려하는 것이 도움이 됩니다.
Factory 패턴과 함께 사용되는 패턴
Factory 패턴과 함께 사용하면 좋은 패턴은 추상 팩토리(Abstract Factory) 패턴입니다. 추상 팩토리 패턴은 관련된 객체들을 생성하는 인터페이스를 제공하여 객체 생성에 대한 책임을 분리합니다.
Factory 패턴과 함께 자주 등장하는 패턴으로는 Singleton 패턴이 있습니다. Singleton 패턴은 단일 인스턴스를 보장하고, 팩토리 클래스 내에서 단일 인스턴스를 생성할 수 있습니다.