2022. 10. 11. 14:24ㆍ카테고리 없음
팩토리 메소드 패턴이란, 구체적인 인스턴스의 생성과정을 서브 클래스에게 맡기는 패턴을 의미한다.
다양한 구현체가 있을 경우, 특정한 구현체를 만들 수 있는 팩토리, 즉 creator를 제공하는 메소드를 구현하는 것을 팩토리 메소드 패턴이라 할 수 있다.
이펙티브 자바를 읽어본 사람은 헷갈릴 수 있지만, 이 패턴은 정적 팩토리 메소드와는 전혀 다른 개념이다.
- 팩토리 메소드 패턴은 인스턴스의 생성 과정을 서브 클래스에게 위임하는 패턴을 정의하고 있고
- 정적 팩토리 메소드는 인스턴스 생성 자체를 좀 더 효율적으로(네이밍을 통한 가시성 확보 및, 불필요한 중복 인스턴스 생성 방지 등) 개선해보고자 하는 일종의 코드 방법론을 정의하고 있다.
팩토리 메소드 패턴에 대한 특징은 우선 코드를 보면서 이야기를 해보자
예제 코드
특정한 상품을 주문하는 코드를 작성해야하는 상황을 가정해보자. 대부분의 상품이 그러하듯, 모든 상품의 모델을 `Product` 객체 하나로 집어 넣기에는 특징이 너무 다양하다. 그래서 대체로 공통분모를 모아 메인 클래스로 작성하고, 경우에 따라 메인 클래스를 확장하여 사용한다. 코드로 표현하면, 다음과 같을 것이다.
Product.java
public class Product {
private String name;
private String brand;
public Product() {
}
public Product(String name, String brand) {
this.name = name;
this.brand = brand;
}
// setter getter
}
FancyProduct.java
public class FancyProduct extends Product {
public FancyProduct() {
setName("Fancy product");
setBrand("Fancy Brand");
}
}
NormalProduct.java
public class NormalProduct extends Product {
public NormalProduct() {
setName("Normal Product");
setBrand("Normal Brand");
}
}
자 이제, Client 클래스를 작성하여, `FancyProduct`와 `NormalProduct`를 주문하는 코드를 작성해보자
Client.java
public class Client {
public void orderFancyProduct(String name, String brand) {
Product product = new FancyProduct();
System.out.println(System.out.format("brand %s name %s order!", product.getBrand(), product.getName()));
}
public void orderNormalProduct(String name, String brand) {
Product product = new NormalProduct();
System.out.println(System.out.format("brand %s name %s order!", product.getBrand(), product.getName()));
}
public static void main(String[] args) {
Client client = new Client();
client.orderFancyProduct("Normal", "NormalBrand");
client.orderNormalProduct("Fancy", "FancyBrand");
}
}
Client 코드를 설명해보면, NormalProduct를 주문할 때와 FancyProduct를 주문할 때의 사용하는 메소드가 다르다. 논리적으로 같은 기능을 수행함에도 불구하고, 같은 코드를 중복 작성하게 되었다.
만약에 주문하는 메소드를 하나로 통일하여, Product 서브 클래스 추가시 코드 변경의 범위를 최대한 줄이고 싶다면, 어떻게 해면 될까?
팩토리 메소드 패턴을 활용하여, `Product`의 인스턴스 생성과정을 위임하면 된다.
예제 코드를 작성하기 전에 다이어그램을 공유해보면, 다음과 같다.
간단히 설명해보면, Product 인스턴스 생성을 담당하는 `ProductFactory` 인터페이스를 정의하였고, 상품의 종류에 따라 구현체를 새로 확장하는 방식을 채택했다. 이러한 구조를 활용하면, Client 코드는 어떠한 `ProductFactory` 구현체를 사용할지 정의하기만 하면 되기 때문에, 결과적으로는 `order` 라는 메소드 하나로 통일할 수 있다.
ProductFactory
public interface ProductFactory {
default Product orderProduct(String name, String brand) {
validate(name, brand);
return createProduct(name, brand);
}
Product createProduct(String name, String brand);
private void validate(String name, String brand) {
if (StringUtils.hasText(name) && StringUtils.hasText(brand)) {
throw new IllegalArgumentException("Write product name and brand");
}
}
}
FancyProductFactory
public class FancyProductFactory implements ProductFactory {
@Override
public Product createProduct(String name, String brand) {
return new FancyProduct();
}
}
NormalProductFactory
public class NormalProductFactory implements ProductFactory {
@Override
public Product createProduct(String name, String brand) {
return new NormalProduct();
}
}
Client
public class Client {
public void order(ProductFactory productFactory, String name, String brand) {
Product product = productFactory.orderProduct(name, brand);
System.out.println(System.out.format("brand %s name %s order!", product.getBrand(), product.getName()));
}
}
위 코드는 java 8 이후에 추가된 인터페이스 default 메소드를 활용하였습니다.