Template Method 패턴에서 상위 클래스에서 처리의 뼈대를 만들고, 하위 클래스 에서 구체적인 살을 붙였다.
- 이 패턴을 인스턴스 생성 장면에 적용한 것이 이번에 배울 Factory Method 패턴이다.
Factory Method 패턴에서는 인스턴스 생성 방법을 상위 클래스에서 결정하되, 구체적인 클래스 이름까지는 결정하지 않는다.
- 구체적인 살은 모두 하위 클래스에서 붙인다.
- 인스턴스 생성을 위한 뼈대(프레임워크)와 실제 인스턴스를 생성하는 클래스를 나누어 생각할 수 있다.
- 인스턴스를 생성하는 공장을 Template Method 패턴으로 구성한 것이 Factory Method 패턴이다.
신분증 카드(ID카드)를 만드는 공장 예제
패키지 | 이름 | 설명 |
framework | Product | 추상 메서드 use만 정의한 추상 클래스 |
framework | Factory | 메서드 create를 구현한 추상 클래스 |
idcard | IDCard | 메서드 use를 구현한 클래스 |
idcard | IDCardFactory | 메서드 createProduct, registerProduct를 구현한 클래스 |
이름 없음 | Main | 동작 테스트용 클래스 |
-인스턴스를 생성하는 프레임워크 쪽(framework 패키지)
-구체적인 내용을 구현하는 쪽(idcard 패키지)
- 공개용 패키지를 개발하는 경우 도메인 이름을 반대로 한 문자열을 이용해 세상에 하나뿐인 패키지 이름을 만들자
인스턴스를 생성하는 뼈대(프레임워크) 역활을 하며 framework 패키지에 속해있는 클래스
‘use를 제품으로 규정’
Product 클래스 - ‘제품’을 표현한 클래스
추상 메서드 use만 선언되어 있으며 use의 구현은 모두 Product의 하위 클래스에 맡긴다.
package factorymethod.framework;
public abstract class Product {
public abstract void use();
}
Factory 클래스 - Template Method 패턴이 사용된다.,
추상 메서드 createProduct로 ‘제품을 만들고’ , 만든 제품을 추상 메서드 registerProduct로 ‘등록’한다.
- 구현은 하위 클래스에서
package factorymethod.framework;
public abstract class Factory {
public final Product create(String owner){ //구현은 하위 클래스
Product p = createProduct(owner); //매개변수로 받은 문자열로 제품을 만든다.
registerProduct(p); //만든 제품을 등록한다.
return p;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
- 이 프레임워크에서 공장이란 ‘create’메서드로 Product 인스턴스를 생성하는 것’ 으로 규정하고 있다.
- createProduct 제품을 만들고 registerProduct로 등록하는 절차로 구현되어 있다.
구체적인 구현 내용은 Factory Method 패턴을 적용한 프로그램에 따라서 달라진다.
Factory Method 패턴에서는 인스턴스를 생성할 때 Template Method 패턴을 사용한다.
- 뼈대에 살을 붙여 구체적인 내용을 구현하는 역활을 하며 idcard 패키지에 속해있다.
IDCard 클래스
인식번호 카드를 나타내는 IDCard 클래스를 만들어 보자
- 프레임워크에서 분리되는 것임을 명시하고자 idcard 패키지라는 별도의 패키지를 만들고 Product(제품)클래스의 하위 클래스로서 정의하자
package factorymethod.idcard;
import factorymethod.framework.Product;
public class IDCard extends Product {
private String owner;
public IDCard(String owner){
System.out.println(owner + "의 카드를 만듭니다.");
this.owner = owner;
}
@Override
public void use() {
System.out.println(this + "을 사용합니다.");
}
@Override
public String toString(){
return "[IDCard:"+owner+"]";
}
public String getOwner(){
return owner;
}
}
IDCardFactory 클래스
IDCard인스턴스를 생성하여 실제로 제품을 만드는 createProduct() 메서드와 만들어진 제품을 등록하는 registerProduct() 메서드를 구현한다.
- 원래라면 DB나 webService에 접속하여 제품을 등록하지만 여기서는 “~을 등록했습니다.” 메시지 출력
package factorymethod.idcard;
import factorymethod.framework.Factory;
import factorymethod.framework.Product;
public class IDCardFactory extends Factory {
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
protected void registerProduct(Product product) {
System.out.println(product + "를 등록했습니다.");
}
}
Main 클래스 - 동작 테스트를 위한 클래스
package factorymethod;
import factorymethod.framework.Factory;
import factorymethod.framework.Product;
import factorymethod.idcard.IDCardFactory;
public class FactoryMain {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("Hyunwoo Ryu");
Product card2 = factory.create("java");
Product card3 = factory.create("designPattern");
card1.use();
card2.use();
card3.use();
}
}
/* 출력
Hyunwoo Ryu의 카드를 만듭니다.
[IDCard:Hyunwoo Ryu]를 등록했습니다.
java의 카드를 만듭니다.
[IDCard:java]를 등록했습니다.
designPattern의 카드를 만듭니다.
[IDCard:designPattern]를 등록했습니다.
[IDCard:Hyunwoo Ryu]을 사용합니다.
[IDCard:java]을 사용합니다.
[IDCard:designPattern]을 사용합니다.
*/
framework 패키지와 idcard 패키지를 이용하여 실제로 IDcard를 만들어 사용
Fatort Mehtod 클래스 다이어그램
Factory Method 패턴의 등장인물
Product(제품) 역 - Product 클래스
프레임워크 쪽으로 이 패턴으로 생성되는 인스턴스가 가져야 할 인터페이스(API)를 결정하는 추상 클래스입니다.
- 구체적인 내용은 하위 클래스 ConcreteProduct에서 결정한다.
Creator(작성자) 역 - Factory 클래스
프레임워크 쪽으로 Product 역을 생성하는 추상 클래스이다.
- 구체적인 내용은 하위 클래스 ConcreteCreator가 결정한다.
- Creator역은 실제로 생성할 ConcreteProduct 역에 대해서는 아무것도 모른다.
- Creator역이 아는 것은 Product 역과 인스턴스 생성 메서드를 호출하면 Product가 생성된다는 것 뿐이다.
- 예제에서 createProduct가 인스턴스를 생성하는 메서드이다.
new를 사용해 실제 인스턴스를 생성하는 대신에, 인스턴스를 생성하는 메서드를 호출함으로써
구체적인 클래스 이름에 의한 속박에서 상위 클래스를 자유롭게 한다.
ConcreteProduct(구체적인 제품) 역 - IDCard 클래스
구체적으로 살을 붙이는 쪽으로, 구체적인 제품을 결정한다.
ConcreteCreator(구체적인 작성자) 역 - IDCardFactory 클래스
구체적으로 살을 붙이는 쪽으로, 구체적인 제품을 만들 클래스를 결정한다.
프레임워크와 구체적인 내용 - framework 패키지와 idcard 패키지로 나뉘어 있다.
같은 framework를 사용하여 다른 ‘제품’과 ‘공장’을 만든다고 했을 때
- framework 패키지 안에는 idcar 패키지를 import하지 않았다.
- Product 클래스나 Factort 클래스에 IDcard나 IDcardFactory라는 구체적인 클래스의 이름이 적혀있지 않았다.
- 즉, 새로운 클래스를 같은 프레임워크로 생성하는 경우에도 framework 패키지는 수정할 필요가 없다.
- framework 패키지는 idcard 패키지에 ‘의존하지 않는다’고 표현한다.
인스턴스 생성 - 메서드 구현 방법
Factory 클래스의 createProduct 메서드는 추상 메서드이다.
- 이 메서드는 하위 클래스에서 구현해야 한다.
createProduct 메서드를 기술하는 방법은 2가지로 생각할 수 있다.
1. 추상 메서드로 기술한다.
- 추상 메서드로 기술하면, 하위 클래스는 반드시 이 메서드를 구현해야만 한다.
- 구현되어 있지 않으면 컴파일할 때 검출된다.
예제에서 이방법 사용
public abstract class Factory {
protected abstract Product createProduct(String owner);
...
}
protected 접근제어자로 상속받은 클래스만 사용가능
2. 디폴트 구현을 준비해 둔다.
- 디폴트 구현을 준비해 두면, 하위 클래스에서 구현하지 않은 경우에 디폴트 구현이 사용된다.
public class Factory {
protected Product createProduct(String owner){
return new Product(owner);
}
...
}
- 이 경우에는 Product 클래스에 대해 직접 new를 실행하므로 Product 클래스를 추상 클래스로 둘 수는 없다.
패턴 이용과 개발자 간의 의사소통
Template Method, Factory Method 패턴들은 실제로 하는 일에 비해서는 복잡한 프로그래밍으로 느껴진다.
- 하나의 클래스만 읽어서는 동작을 잘 이해할 수 없기 때문이다.
- 상위 클래스에서 동작의 뼈대를 이해한 후 거기에서 사용되는 추상 메서드가 무엇인지 확인하고, 다시 그 추상 메서드를 실제로 구현하는 클래스의 소스 코드를 살펴봐야 한다.
일반적으로 디자인 패턴을 사용해 어떤 클래스군을 설계할 경우, 그 클래스군을 보수하는 사람에게 설계자가 의도한 디자인 패턴이 무엇인지 잘 전달할 필요가 있다.
- 전달하지 않으면 설계자의 처음 의도에서 벗어난 수정이 가해질 수 있다.
- 프로그램의 주석이나 개발 문서 안에 실제로 사용되는 디자인 패턴의 명칭과 의도를 기술해 두는 것도 좋은 방법이다.
static Factory Method
인스턴스 생성을 위한 클래스 메서드(static 메서드) 전반을 Factory Method라고 부르는 경우가 있다.
- GoF의 Factory Method 패턴과는 다르지만, Java에서 인스턴스를 생성할 때 매우 자주 사용되는 기법이다.
- Java API 레퍼런스에서도 인스턴스 생성을 위한 클래스 메서드를 static Factory Method로 표현하기도 한다.
Java API 레퍼런스를 읽을 때는 참조하는 클래스에 static Factory Method가 제공되는지 주목하자!
- 이는 인스턴스 생성에서 중요한 역활을 하는 경우가 많기 때문이다.
- static Factory Method로서는 create, newInstance, getInstance 등의 이름이 자주 사용되지만 그 밖에 이름이 사용되는 경우도 있다.
java.security.SecureRandom의 getInstance 메서드는 난수 생성 알고리즘 이름을 지정해서 SecureRandom 인스턴스를 생성하는 static Factory Method이다.
SecureRandom random = SecureRandom.getInstance("NativePRNG");
java.util.List; 의 of 메서드는 구체적인 요소를 주면 List 인스턴스를 생성하는 static Factory Method 이다.
List<String> list = List.of("Alice", "Bob", "Chris");
java.util.Arrays; 의 asList 메서드는 지정된 배열이나 열거한 요소로부터 List 인스턴스를 생성하는 static Factory Method 이다.
String[] arr = {"Alice", "Bob", "Chris"};
List<String> list1 = Arrays.asList(arr);
List<String> list2 = Arrays.asList("Alice", "Bob", "Chris");
java.lang.String의 valueOf 메서드는 다양한 형태의 문자열 표현을 얻는 static Factory Method 이다.
//char형 'A'로부터 String형 "A"를 얻는다.
String str = String.valueOf('A');
java.time.Instant; 의 now 메서드는 현재 시간을 나타내는 Instant의 인스턴스를 생성하는 static Factory Method 이다. 여기서느 생성한 인스턴스가 무엇인지 알기 쉽게 now(지금)라는 이름으로 되어 있다.
Instant instant = Instant.now();
관련 패턴
Template Method 패턴
Singleton 패턴
Composite 패턴
Iterator 패턴
'책 > Java 언어로 배우는 디자인 패턴 입문' 카테고리의 다른 글
[디자인 패턴] Prototype패턴 (0) | 2023.03.12 |
---|---|
[디자인 패턴] Singleton패턴 (0) | 2023.03.11 |
[디자인 패턴] Template Method패턴 (0) | 2023.03.11 |
[디자인 패턴] Adapter패턴 (0) | 2023.03.11 |
[디자인 패턴] Iterator패턴 (0) | 2023.02.10 |