팩토리 메소드 패턴

개념

  • 객체를 사용하는 코드에서 객체 생성 부분을 떼어내어 추상화한 패턴

  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정한다.

  • 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 결정한다.

  • 다양한 구현체가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리를 제공할 수 있다.

예시 1. 가장 단순한 형태의 팩토리 매서드 패턴 - 푸트코트 도시락


public class FoodCourt {
    public static void main(String[] args) {
        Dosirak defaultDosirak = DosirakFactory.order("Default", "spam");
        Dosirak spamDosirak = DosirakFactory.order("SpamDosirak", "spam");
        Dosirak tunaDosirak = DosirakFactory.order("TunaDosirak", "tuna");

        System.out.println("defaultDosirak = " + defaultDosirak);
        System.out.println("spamDosirak = " + spamDosirak);
        System.out.println("tunaDosirak = " + tunaDosirak);
    }
}


abstract class Dosirak {
    public abstract String mainIngredient();

    @Override
    public String toString() {
        return "Here is " + this.mainIngredient();
    }
}

class DosirakFactory {
    public static Dosirak order(String type, String mainIngredient){
        if ("SpamDosirak".equalsIgnoreCase(type)) {
            return new SpamDosirak(mainIngredient);
        }

        if ("TunaDosirak".equalsIgnoreCase(type)) {
            return new TunaDosirak(mainIngredient);
        }

        return new DefaultDosirak();
    }
}

class SpamDosirak extends Dosirak {

    private final String main;

    public SpamDosirak(String main) {
        this.main = main;
    }

    @Override
    public String mainIngredient() {
        return this.main;
    }
}

class TunaDosirak extends Dosirak {

    private final String main;

    public TunaDosirak(String main) {
        this.main = main;
    }

    @Override
    public String mainIngredient() {
        return this.main;
    }
}


class DefaultDosirak extends Dosirak {

    private final String main;

    public DefaultDosirak() {
        this.main = "김치";
    }

    @Override
    public String mainIngredient() {
        return this.main;
    }
}

/**
 * defaultDosirak = Here is 김치
 * spamDosirak = Here is spam
 * tunaDosirak = Here is tuna
 * */

예시 2. 조금 더 복잡한 형태의 패턴 - 배 만들기

적용 전

적용 후

  • product 에 해당하는 ship도, creator 에 해당하는 factory 도 모두 팩토리 매소드 패턴을 적용하여 변경 가능성은 줄이고, 확장가능성은 높인다.

자세히보기

  • 팩토리 매소드 패턴을 적용했을 때의 장점과 단점

    • 기준점이 되는 ship 인터페이스나 shipFactory 클래스는 전혀 변경하지 않고도, 쉽게 새로운 유형인 BlueShip, BlackShip 등 을 추가할 수 있다. 즉, 확장에는 열려있고, 변경에는 닫혀있다.

    • 팩토리 매소드 패턴을 사용하면 확장성이 좋지만, 동시에 해당 패턴을 사용하지 않았을 때보다 클래스 구조도는 훨씬 복잡해지게 된다.

  • 확장에 열려있고, 변경에 닫혀있는 객체지향의 원칙이란

    • 새로운 유형을 추가할 때, 기준이 되는 인터페이스나 클래스의 코드는 전혀 변경하지 않고, 기존의 유형에 상관없이 새로운 유형을 쉽게 추가할수 있는 구조를 말한다.

    • 기존 코드를 변경하지 않고, 기존 것을 확장하여 새로운 것을 만드는 것이 쉽다.

  • 자바 8에 추가된 default 매소드에 대해서 설명

    • 자바 8부터는 interface 에서 default 키워드를 붙인 매소드를 미리 구현해놓아 하위 클래스들에서 새롭게 구현하지 않을 경우, 호출되는 디폴트 매소드로 이용할 수 있다.

    • 참고로 자바 9부터는 interface에서 private 키워드를 붙여 매소드를 구현해놓을 수 있다.

    • 따라서 자바 9 이후로는 사실 추상매소드 쓸 일이 별로 없다.

실제 예시

자바

  • 실제 예시에서는 위의 예시 1에서처럼 단순한 팩토리 패턴이 많이 쓰인다. 매개변수의 값에 따라서 내부에서 분기처리하여 서로 다른 인스턴스를 반환하는 구조.

  • 예를 들면, 자바의 java.lang.Calendar, java.lang.NumberFormat 등이 그러하다.

public class SimpleFactory {

    public Object createProduct(String name) {
        if (name.equals("whiteship")) {
            return new Whiteship();
        } else if (name.equals("blackship")) {
            return new Blackship();
        }

        throw new IllegalArgumentException();
    }
}
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

    ...

    private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
        CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale).getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException var7) {
            }
        }
    
        Calendar cal = null;
        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                byte var6 = -1;
                switch(caltype.hashCode()) {
                case -1581060683:
                    if (caltype.equals("buddhist")) {
                        var6 = 0;
                    }
                    break;
                case -752730191:
                    if (caltype.equals("japanese")) {
                        var6 = 1;
                    }
                    break;
                case 283776265:
                    if (caltype.equals("gregory")) {
                        var6 = 2;
                    }
                }
    
                switch(var6) {
                case 0:
                    cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case 1:
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case 2:
                    cal = new GregorianCalendar(zone, aLocale);
                }
            }
        }
    
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
    
        return (Calendar)cal;
    }
}

스프링

  • spring bean factory

    • object 타입의 product 를 만드는 BeanFactory라는 Creator

public class SpringBeanFactoryExample {

    public static void main(String[] args) {
        BeanFactory xmlFactory = new ClassPathXmlApplicationContext("config.xml");
        String hello = xmlFactory.getBean("hello", String.class);
        System.out.println(hello);

        BeanFactory javaFactory = new AnnotationConfigApplicationContext(Config.class);
        String hi = javaFactory.getBean("hello", String.class);
        System.out.println(hi);
    }
}

Last updated