객체 지향 설계 기법 중에서 팩토리 메서드(factory method) 패턴과 빌더(Builder) 패턴이 있다. 공장 역할을 하는 객체를 통해 필요한 인스턴스를 간접적으로 얻는 방식이다. 이번 절에서는 공장 역할을 하는 객체를 통해 인스턴스를 얻는 빈 설정 방법을 배워본다.

인스턴스를 생성하는 일반적인 방법은 new 명령을 사용하는 것이다. new 명령을 통해 클래스와 생성자를 지정하면 해당 클래스의 인스턴스가 생성되고 초기화된다(그림 1). 문제는 인스턴스 생성 작업이 복잡한 경우이다. 매번 인스턴스를 생성할 때마다 복잡한 과정을 거쳐야 한다면 코딩할 때 부담이 된다. 이를 해결하기 위해 나온 것이 팩토리 메서드 패턴과 빌더 패턴이다.

팩토리 클래스는 인스턴스 생성 과정을 캡슐화한다. 즉 인스턴스를 생성하고 초기화하는 복잡한 과정을 감추기 때문에 개발자 입장에서는 인스턴스를 얻는 과정이 간결해진다(그림 2). 이번 실습은 Tire 클래스에 대해 팩토리 메서드 패턴을 적용하고, 팩토리 객체를 통해 인스턴스를 얻으려면 빈을 어떻게 설정해야 하는지 그 방법을 배워본다.
1. 스태틱 팩토리 메서드를 이용한 간접 객체 생성
팩토리 메서드를 만들 때는 두 가지 방법이 있다. 스태틱으로 선언하여 클래스 메서드로 만들거나 아니면 인스턴스 메서드로 만드는 것이다. 먼저 스태틱으로 선언된 팩토리 메서드를 설정하는 방법을 알아본다.
- 실습 패키지 생성
exam 패키지 아래에 test11 패키지를 생성한다. test10 패키지의 모든 파일을 복사해온다.
- 팩토리 클래스 생성
exam.test11 패키지에 TireFactory 클래스를 생성하고 다음과 같이 편집한다.
package exam.test11;
import java.text.SimpleDateFormat;
import java.util.Properties;
public class TireFactory {
/**
* 팩토리 메서드
* @param maker
* @return createHankookTire(), createKumhoTire()
*/
public static Tire createTire(String maker) {
if(maker.equals("Hankook")) {
return createHankookTire();
}else {
return createKumhoTire();
}
}
/**
* Hankook 타이어 생성
* @return tire
*/
private static Tire createHankookTire() {
Tire tire = new Tire();
tire.setMaker("Hankook");
Properties specProp = new Properties();
specProp.setProperty("width", "205");
specProp.setProperty("ratio", "65");
specProp.setProperty("rim.diameter", "14");
tire.setSpec(specProp);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
tire.setCreatedDate(dateFormat.parse("2014-5-5"));
}catch (Exception e) {}
return tire;
}
/**
* Kumho 타이어 생성
* @return tire
*/
private static Tire createKumhoTire() {
Tire tire = new Tire();
tire.setMaker("Kumho");
Properties specProp = new Properties();
specProp.setProperty("width", "185");
specProp.setProperty("ratio", "75");
specProp.setProperty("rim.diameter", "16");
tire.setSpec(specProp);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
tire.setCreatedDate(dateFormat.parse("2014-3-1"));
}catch (Exception e) {}
return tire;
}
}
/**
* 팩토리 메서드
* @param maker
* @return createHankookTire(), createKumhoTire()
*/
public static Tire createTire(String maker) {
if(maker.equals("Hankook")) {
return createHankookTire();
}else {
return createKumhoTire();
}
}
createTire() 메서드는 팩토리 메서드이다. 매개변수 값으로 제조사 이름을 주면 해당 제조사의 타이어를 생산하는 메서드이다. 이 메서드는 static으로 선언되었다. 일반 클래스의 메서드를 공장(factory) 기능을 하도록 하려면 반드시 static으로 선언해야 한다. 이것이 스프링 IoC 컨테이너의 규정이다.
private static Tire createHankookTire() {...}
private static Tire createKumhoTire() {...}
createTire() 메서드에서 호출하는 메서드가 두 개 있다. createHankookTire() 메서드는 'Hankook' 회사의 타이어를 생성해주는 메서드이고, createKumhoTire() 메서드는 'Kumho' 타이어를 생성해 준다.
두 메서드 모두 private으로 선언하였다. 외부에서 호출할 일이 없기 때문이다. 메서드는 무조건 public으로 공개해야 한다고 생각하는 경우가 많은데, 그렇지 않다. 지금처럼 내부에서 사용할 목적으로 만든 메서드는 private으로 선언해서 외부에서 호출하지 못하도록 막는 것이 좋다.
createTire() 메서드는 스태틱(static) 메서드이기 때문에 스태틱 멤버(변수 및 메서드)만 직접 접근할 수 있다. 인스턴스 메서드는 인스턴스가 있어야 하므로, createHankookTire() 메서드와 createKumhoTire() 메서드를 static으로 선언하였다. 그럼 한 번 createHankookTire() 메서드를 자세히 살펴본다.
private static Tire createHankookTire() {
Tire tire = new Tire();
tire.setMaker("Hankook");
...
}
먼저 Tire 인스턴스 생성한 후 maker 프로퍼티의 값을 "Hankook"이라고 설정한다.
Properties specProp = new Properties();
specProp.setProperty("width", "205");
specProp.setProperty("ratio", "65");
specProp.setProperty("rim.diameter", "14");
tire.setSpec(specProp);
타이어의 규격을 설정하기 위해 Properties 객체를 생성한다. setProperty() 메서드를 사용하여 규격 정보를 설정한다. 규격 정보를 담은 Properties 객체를 'spec' 프로퍼티에 할당한다.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
"2014-5-5" 형식의 문자열을 java.util.Date 객체로 만들어 주는 SimpleDateFormat 객체를 생성한다. 생성자에 문자열의 날짜 형식을 지정한다. yyyy는 네 자리 년(year)도를 의미한다. 대문자로 된 MM은 월(month)을 의미한다. 소문자 mm은 분(minute)을 의미한다. dd는 일(day)을 의미한다. 대문자 DD는 1년을 기준으로 한 일자(예 : 178일)를 의미한다.
try {
tire.setCreatedDate(dateFormat.parse("2014-5-5"));
}catch (Exception e) {}
return tire;
SimpleDateFormat 객체의 parse() 메서드를 사용하여 "2014-5-5" 형식의 문자열로부터 java.util.Date 객체를 생성한다. 생성된 Date 객체는 tire의 'createdDate' 프로퍼티에 할당한다. 이렇게 준비한 타이어 객체를 메서드 호출자에게 반환한다. createKumhoTire() 메서드도 createHankookTire() 메서드와 마찬가지로 동작한다.
- beans.xml 빈 설정 파일
앞에서 만든 팩토리 클래스를 사용하여 빈을 생성하는 방법은 다음과 같다.
exam/test11/beans.xml 파일을 열고 다음과 같이 편집한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hankookTire" class="exam.test11.TireFactory" factory-method="createTire">
<constructor-arg value="Hankook"/>
</bean>
<bean id="kumhoTire" class="exam.test11.TireFactory" factory-method="createTire">
<constructor-arg value="Kumho"/>
</bean>
</beans>
<bean id="hankookTire" class="exam.test11.TireFactory" factory-method="createTire">
<constructor-arg value="Hankook"/>
</bean>
▶ 자바 코드로 표현하면?
Tire hankookTire = TireFactory.createTire("Hankook");
빈의 class 속성에 팩토리 클래스 이름을 지정한다. 여기서 중요한 것은 factory-method 속성의 값이다. 이 값은 Tire 인스턴스를 생성할 메서드 이름이다. factory-method 속성에는 반드시 static 메서드 이름을 지정해야 한다. 팩토리 메서드에 넘겨 줄 매개변수 값은 <constructor-arg> 태그로 설정한다(그림 3).

createTire() 메서드와 같은 팩토리 메서드는 빈 컨테이너가 인스턴스를 생성할 때 딱 한 번 호출된다. 헷갈리지 말아야 할 것은 'hankookTire'라는 아이디로 등록된 객체는 TireFactory 인스턴스가 아니라 Tire 인스턴스라는 사실이다. 즉 팩토리 메서드, createTire() 메서드가 반환한 객체이다.
<bean id="kumhoTire" class="exam.test11.TireFactory" factory-method="createTire">
<constructor-arg value="Kumho"/>
</bean>
▶ 자바 코드로 표현하면?
Tire kumhoTire = TireFactory.createTire("Kumho");
아이디 'kumhoTire'는 createTire() 메서드가 반환한 빈을 가리킨다. 실제는 createKumhoTire() 메서드가 반환한 빈이다.
※ 자바 객체(Java Object) vs 인스턴스(instance) vs 빈(bean)
new 명령어나 팩토리 메서드로 생성된 '인스턴스'를 일반적으로 '자바 객체'라 부른다. 스프링에서는 자바 객체를 '빈'이라고 부른다. 일상 생활에서도 하나의 사물을 다양하게 표현하듯이, 예를 들어 물을 음료라고 부르기도 하고 '음료수', '마실 것' 등으로 부르는 것처럼, 프로그래밍 세계에서도 하나의 개념에 대해 다양한 말로 표현한다.
이 책에서는 가능한 다양한 표현에 익숙하도록 '인스턴스'를 '자바 객체', '객체', '빈'으로 바꿔가면서 사용할 것이다. 그러니 헷갈려 하지 마시길!
- 빈 컨테이너 테스트
팩토리 메서드를 통해 생성된 빈을 확인해 본다. exam.test11.Test 클래스를 다음과 같이 편집한다.
package exam.test11;
import java.util.Map.Entry;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//IoC 컨테이너 준비하여 빈 생성
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("exam/test11/beans.xml");
Tire t1 = (Tire)ctx.getBean("hankookTire");
Tire t2 = (Tire)ctx.getBean("kumhoTire");
Tire t3 = (Tire)ctx.getBean("hankookTire");
System.out.println("t1-->" + t1.toString());
System.out.println("t2-->" + t2.toString());
System.out.println("t3-->" + t3.toString());
if(t1 != t2) {
System.out.println("t1 != t2");
}
if(t1 == t3) {
System.out.println("t1 == t3");
}
}
}
Tire t1 = (Tire)ctx.getBean("hankookTire");
Tire t2 = (Tire)ctx.getBean("kumhoTire");
if(t1 != t2) {
System.out.println("t1 != t2");
}

'hankookTire'로 등록된 빈을 꺼낸다. createHankookTire() 메서드가 만든 빈이다. 'kumhoTire'로 등록된 빈을 꺼낸다. createKumhoTire() 메서드가 만든 빈이다. 당연히 두 빈을 비교해보면 다르다고 나올 것이다.
Tire t3 = (Tire)ctx.getBean("hankookTire");
if(t1 == t3) {
System.out.println("t1 == t3");
}

다시 빈 컨테이너에서 'hankookTire'로 등록된 빈을 꺼낸다. 이전에 꺼낸 t1과 같은 빈이다. getBean() 메서드는 호출될 때마다 팩토리 메서드를 호출하지 않는다. 처음 빈을 만들 때 딱 한 번 호출한다.
참고도서 : https://freelec.co.kr/book/1674/
[열혈강의] 자바 웹 개발 워크북
[열혈강의] 자바 웹 개발 워크북
freelec.co.kr
'교재 실습 > 자바 웹 개발 워크북' 카테고리의 다른 글
144. 빈의 범위 설정 (0) | 2022.11.07 |
---|---|
143. 팩토리 메서드와 팩토리 빈 (2) (0) | 2022.11.06 |
141. 컬렉션 값 주입 (2) (0) | 2022.10.26 |
140. 컬렉션 값 주입 (1) (0) | 2022.10.25 |
139. 의존 객체 주입 (2) (0) | 2022.10.24 |
댓글