추상 팩토리 메서드(Abstract Factory Pattern) 패턴

weekbelt·2022년 1월 17일
2

디자인패턴

목록 보기
3/4

예제 코드

이 예에서 AnimalFactory와 ColorFactory의 두 가지 Factory를 구현하겠습니다. 그런 다음 AbstractFactory를 사용하여 접근 권한을 관리합니다.

먼저 Animal 인터페이스를 생성합니다.

public interface Animal {
    String getAnimal();
    String makeSound();
}

그리고 Animal인터페이스의 구현체인 Duck클래스를 생성합니다.

public class Duck implements Animal {
	
    @Override
    public String getAnimal() {
    	return "Duck";
    }
    
    @Override
    public String makeSound() {
    	return "Squeks";
    }
}

또한 이러한 방식으로 동물 인터페이스(Dog, Bear 등)를 보다 구체적으로 구현할 수 있습니다.

이제 여러 제품군을 준비했으므로 이들을 위한 AbstractFactory 인터페이스를 만들 수 있습니다.

public interface AbstractFactory<T> {
    T create(String animalType);
}

그다음으로 팩토리 메서드 패턴을 활용하여 AnimalFactory를 생성하겠습니다.

public class AnimalFactory implements AbstractFactory<Animal> {
    
    @Override
    public Animal create(String animalType) {
        if ("Dog".equalsIgnoreCase(animalType)) {
            return new Dog();
        } else if ("Duck".equalsIgnoreCase(animalType)) {
            return new Duck();
        }
        return null;
    }
}

이 모든 설정이 완료되면 GetFactory()이 제공하는 인수에 따라 AnimalFactory를 제공하는 FactoryProvider 클래스를 만들 것입니다.

public class FactoryProvider {
    public static AbstractFactory getFactory(String choice){
        
        if("Animal".equalsIgnoreCase(choice)){
            return new AnimalFactory();
        }
        else if("Color".equalsIgnoreCase(choice)){
            return new ColorFactory();
        }
        
        return null;
    }
}

Java 에서 추상 팩토리 메소드 사용 예

JDK에서 추상 팩토리 메서드 패턴의 예로 DocumentBuilderFactory클래스의 newInstance 메소드가 해당됩니다. DocumentBuilderFactory는 xml파일을 Document로 파싱해오는 예제코드 입니다.

import java.io.ByteArrayInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

public class DocumentBuilderExample {
  public static void main(String[] args) throws Exception {
    String testXml = "<note><to>Receiver</to><from>Sender</from><heading>SubjectHeading</heading><body>Body text under the body tag.</body></note>";
    
    ByteArrayInputStream ba = new ByteArrayInputStream(testXml.getBytes());
    
    DocumentBuilderFactory absFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder factory = absFactory.newDocumentBuilder();
    Document doc = factory.parse(ba);
    doc.getDocumentElement().normalize();
    
    System.out.println("Root Node Name is ::: " + doc.getDocumentElement().getNodeName());
    System.out.println("get abstract factory class name is ::: " + absFactory.getClass());
    System.out.println("get abstract factory class name is ::: " + factory.getClass());
  }
}

DocumentBuilderFactory가 시스템 속성을 읽어 인스턴스를 생성하기때문에 Client입장에서는 내부적인 구현을 알 필요가 없이 newInstance() 메서드를 통해 팩터리를 생성하면 됩니다.

public abstract class DocumentBuilderFactory {
//.....
    public static DocumentBuilderFactory newInstance() {
        return FactoryFinder.find(
                /* The default property name according to the JAXP spec */
                DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
                /* The fallback implementation class name */
                DEFAULT_IMPL);
//.....
    }

}

FactoryFinder의 find메서드를 따라가보면 3가지 분기를 통해 인스턴스를 생성하고 있습니다.

    static <T> T find(Class<T> type, String fallbackClassName)
        throws FactoryConfigurationError
    {
        final String factoryId = type.getName();
        dPrint(()->"find factoryId =" + factoryId);

        // Use the system property first
        try {
            String systemProp = SecuritySupport.getSystemProperty(factoryId);
            if (systemProp != null) {
                dPrint(()->"found system property, value=" + systemProp);
                return newInstance(type, systemProp, null, true);
            }
        }
}

처음에 시스템 속성이 null이 아닐경우 시스템 속성을 통해서 인스턴스를 생성하고 있습니다.

        try {
            if (firstTime) {
                synchronized (cacheProps) {
                    if (firstTime) {
                        String configFile = SecuritySupport.getSystemProperty("java.home") + File.separator +
                            "conf" + File.separator + "jaxp.properties";
                        File f = new File(configFile);
                        firstTime = false;
                        if (SecuritySupport.doesFileExist(f)) {
                            dPrint(()->"Read properties file "+f);
                            cacheProps.load(SecuritySupport.getFileInputStream(f));
                        }
                    }
                }
            }
            final String factoryClassName = cacheProps.getProperty(factoryId);

            if (factoryClassName != null) {
                dPrint(()->"found in ${java.home}/conf/jaxp.properties, value=" + factoryClassName);
                return newInstance(type, factoryClassName, null, true);
            }
        }

다음으로 시스템 속성이 null일 경우 $java.home/conf/jaxp.properties이 경로에 있는 설정들을 읽어서 인스턴스를 생성하려고 합니다.

        T provider = findServiceProvider(type);
        if (provider != null) {
            return provider;
        }
        if (fallbackClassName == null) {
            throw new FactoryConfigurationError(
                "Provider for " + factoryId + " cannot be found");
        }

        dPrint(()->"loaded from fallback value: " + fallbackClassName);
        return newInstance(type, fallbackClassName, null, true);

마지막으로 Jar 서비스 Provider를 찾아서 Provider를 리턴하거나 Provider가 없다면 newInstance()를 호출할때 기본설정이 되어있었던 DEFAULT_IMPL인

    private static final String DEFAULT_IMPL =
            "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";

DocumentBuilderFactoryImpl을 생성하여 리턴합니다.

결론

객체 생성을 캡슐화해서 코드와 구상 형식을 분리시킬 수 있게 해 줍니다. 따라서 애플리케이션의 결합을 느슨하게 만들고, 특정 구현에 덜 의존하도록 만들 수 있습니다.

profile
백엔드 개발자 입니다

0개의 댓글