팩토리 패턴(Factory Pattern)은 객체 생성 로직을 캡슐화하여, 객체 생성을 담당하는 별도의 클래스를 제공하는 디자인 패턴이다.
이 패턴을 사용하면 객체를 생성하는 코드와 사용하는 코드를 분리할 수 있어 유지보수성이 향상된다.
가장 기본적인 형태의 팩토리 패턴으로, 팩토리 메서드를 통해 객체를 생성한다.
class Product {
void use() {
System.out.println("제품 사용");
}
}
class ConcreteProductA extends Product {
@Override
void use() {
System.out.println("A 제품 사용");
}
}
class ConcreteProductB extends Product {
@Override
void use() {
System.out.println("B 제품 사용");
}
}
// 팩토리 클래스
class SimpleFactory {
public static Product createProduct(String type) {
if (type.equals("A")) {
return new ConcreteProductA();
} else if (type.equals("B")) {
return new ConcreteProductB();
}
throw new IllegalArgumentException("Unknown product type: " + type);
}
}
// 사용 예시
public class FactoryPatternExample {
public static void main(String[] args) {
Product product = SimpleFactory.createProduct("A");
product.use();
}
}
특징
createProduct() 메서드를 수정해야 한다. 팩토리 메서드 패턴은 객체 생성의 책임을 서브클래스로 위임하는 방식이다.
// 제품 인터페이스
interface Product {
void use();
}
// 구체적인 제품 클래스
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("A 제품 사용");
}
}
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("B 제품 사용");
}
}
// 팩토리 인터페이스
abstract class Factory {
abstract Product createProduct();
public void doSomething() {
Product product = createProduct();
product.use();
}
}
// 구체적인 팩토리 클래스
class ConcreteFactoryA extends Factory {
@Override
Product createProduct() {
return new ConcreteProductA();
}
}
class ConcreteFactoryB extends Factory {
@Override
Product createProduct() {
return new ConcreteProductB();
}
}
// 사용 예시
public class FactoryMethodExample {
public static void main(String[] args) {
Factory factory = new ConcreteFactoryA();
factory.doSomething(); // "A 제품 사용"
}
}
특징
createProduct())에 캡슐화된다. 추상 팩토리 패턴은 서로 관련된 객체군을 생성하는 인터페이스를 제공하는 방식이다.
// 제품 인터페이스
interface ProductA {
void use();
}
interface ProductB {
void interact();
}
// 제품 구현 클래스
class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("A1 제품 사용");
}
}
class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("A2 제품 사용");
}
}
class ConcreteProductB1 implements ProductB {
@Override
public void interact() {
System.out.println("B1 제품과 상호작용");
}
}
class ConcreteProductB2 implements ProductB {
@Override
public void interact() {
System.out.println("B2 제품과 상호작용");
}
}
// 추상 팩토리
interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 구체적인 팩토리 클래스
class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
// 사용 예시
public class AbstractFactoryExample {
public static void main(String[] args) {
AbstractFactory factory = new ConcreteFactory1();
ProductA productA = factory.createProductA();
ProductB productB = factory.createProductB();
productA.use();
productB.interact();
}
}
특징
팩토리 패턴에서는 Class.forName()을 사용하여 동적으로 객체를 생성할 수도 있다.
public class DynamicFactory {
public static Product createProduct(String className) {
try {
return (Product) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("제품 생성 실패", e);
}
}
}
장점
Spring에서는 팩토리 패턴을 직접 구현하지 않고, 스프링 컨테이너가 제공하는 Bean 관리 기능을 활용하는 것이 일반적이다.
@Bean, @Component, @Service 등을 사용하면 Spring이 객체 생성을 관리한다. Spring의 Bean Factory
BeanFactory는 팩토리 패턴을 기반으로 객체를 관리한다. 데이터베이스 드라이버
DriverManager.getConnection()은 팩토리 패턴을 사용하여 적절한 DB 드라이버를 제공한다. 로깅 시스템
LoggerFactory.getLogger(Class<?>)는 팩토리 패턴을 활용한 대표적인 예시다. 팩토리 패턴은 객체 생성을 캡슐화하여 유연성을 높이는 강력한 패턴이다.
하지만 Spring과 같은 프레임워크에서는 직접 구현하기보다 프레임워크가 제공하는 기능을 활용하는 것이 더 효율적이다.
팩토리 패턴을 올바르게 사용하면 유지보수성과 확장성을 높일 수 있다.