팩토리패턴 - 자바 버전, 스프링 Bean 버전

Kim Dong Kyun·2023년 4월 19일
1

팩토리 패턴?

객체의 생성 부분을 캡슐화해서, 객체의 생성 로직이 복잡 할 시 도움이 되는 디자인 패턴.

  • 한 추상에서 다양한 객체의 생성이 필요 할 때, 상위 추상의 타입으로 하위 객체들을 관리하기 위함이다.

  • 말은 좀 어렵지만, 구현체의 인스턴스 생성을 제한함으로써 얻는 이익을 극대화 할 수 있다.

즉, 결합도를 낮출 수 있다. -> 확장에 용이하다 가장 중요한 컨셉이다.

  • 상위 추상의 타입으로 인스턴스를 선언하고, 마치 구현체로 선언한 것 처럼 사용 할 수 있게 해주는 녀석.

  • 스프링은 빈팩토리에서 빈의 생성과 소멸을 관리하는데, 이 빈팩토리가 팩토리 패턴으로 구현되어 있다.


1. 자바 코드

// 제품(Product) 인터페이스
public interface Product {
    void use();
}

// 제품(Product) 구현 클래스: 제품 A
public class ProductA implements Product {
    @Override
    public void use() {
        System.out.println("Product A를 사용합니다.");
    }
}

// 제품(Product) 구현 클래스: 제품 B
public class ProductB implements Product {
    @Override
    public void use() {
        System.out.println("Product B를 사용합니다.");
    }
}

// 팩토리(Factory) 클래스
public class ProductFactory {
    // 제품 생성 메서드
    public Product createProduct(String type) {
        if (type.equals("A")) {
            return new ProductA();
        } else if (type.equals("B")) {
            return new ProductB();
        } else {
            throw new IllegalArgumentException("유효하지 않은 제품 타입입니다.");
        }
    }
}

// 클라이언트(Client) 클래스
public class Client {
    public static void main(String[] args) {
        ProductFactory factory = new ProductFactory();

        // 제품 A 생성
        Product productA = factory.createProduct("A");
        productA.use(); // Output: Product A를 사용합니다.

        // 제품 B 생성
        Product productB = factory.createProduct("B");
        productB.use(); // Output: Product B를 사용합니다.
    }
}

자바 코드는 간단하다.

  1. 두 객체의 상위 추상을 선언하고, 추상 매서드를 선언한다. 이 추상을 구현받는 구현체는 추상의 기능을 수행함을 보장받는다.

  2. "팩토리" 클래스에서는 인스턴스의 생성 로직을 구현한다. 들어온 타입에 따라 어떤 인스턴스를 사용 할 지 정해준다.

  3. Client 클래스에서는 "추상의 타입" 으로 인스턴스를 생성한다.

위와 같이 if문을 사용 해 구현 할 수 있다. 그러나, 스프링에서는 좀 더 예쁜 방식으로 가능 할 것 같다.


2. 스프링

스프링에서는 어떻게 작동할까?

서비스의 추상 인터페이스가 하나 존재하고, 이 구현체가 많으며 더 추가될 가능성마저 있다고 하자. 구현체의 기능은 대동소이하다

( 예를 들어, Oauth2 소셜 로그인의 경우 카카오, 네이버 등 비슷하다)

예제 코드는 자바와 맞추기 위해서 Product로 했다.

// 공통 추상
public interface ProductService {
    void use();
    ProductProvider getProvider();
}

// 추상의 구현체 서비스 1
@Service
public class ProductServiceA implements ProductService {
    @Override
    public void use() {
        System.out.println("use A");
    }

    @Override
    public ProductProvider getProvider() {
        return ProductProvider.ProductA;
    }
}

// 구현체 서비스 2
@Service
public class ProductServiceB implements ProductService {
    @Override
    public void use() {
        System.out.println("use B");
    }

    @Override
    public ProductProvider getProvider() {
        return ProductProvider.ProductB;
    }
}

// 위 구현체들의 타입 명시를 위한 열거형 클래스
public enum ProductProvider {
    ProductA, ProductB
}

// 구현체들을 HashMap에 담고, 적절히 배분해주는 로직을 가지는 팩토리 클래스
@Component
public class ProductProviderFactory {
    private final List<ProductService> products;

    private HashMap<ProductProvider, ProductService> map = new HashMap<>();

    public ProductProviderFactory(List<ProductService> products) {
        this.products = products;
        for (ProductService product : products) {
            this.map.put(product.getProvider(), product);
        }
    }

    public ProductService getService(ProductProvider provider){
        return this.map.get(provider);
    }
}

// 팩토리에서 서비스를 호출해서 use() 매서드를 사용하는 부분
@Component
public class Client {
    private final ProductProviderFactory productProviderFactory;

    public Client(ProductProviderFactory productProviderFactory) {
        this.productProviderFactory = productProviderFactory;
    }

    public void doService(ProductProvider provider){
        ProductService service = productProviderFactory.getService(provider);

        service.use();
    }
}

위와 같이 하나의 추상이 여러 개의 구현체를 가질 때, Factory 클래스를 의존 주입 하는 것으로 여러 개의 구현체 중 필요한 것을 선택해서 배분 할 수 있다.

따라서 Product C, D...확장이 계속 발생하더라도 새롭게 추가되는 코드는 있으나 변경은 일어나지 않는다.

0개의 댓글