2. 팩토리 메소드(Factory Method) 패턴
😉 : ‘코딩으로 학습하는 GoF의 디자인 패턴’ 공부 기록입니다.
팩토리 메소드 패턴이란?
- 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.
- 다양한 구현체가 있고, 구현체를 만들 수 있는 팩토리를 제공한다.
- 인터페이스 안에 기본적인 구현을 만들고, 바뀌어야 하는 부분을 하위 클래스에서 구현하도록 한다. -> 어떤 인스턴스를 생성하는 책임을 구체적인 클래스가 아니라 추상적인 인터페이스의 메소드로 감싸는 것
- 왜? : 어떤 요구사항이 바뀜으로써 기존 코드가 바뀌는 것을 방지하고 (변경에 닫혀있고) 확장에 열려있게 하기 위해서
적용 예시
AS-IS
public class BookFactory {
public static Book orderBook(String type, String address) {
prepareFor(type);
Book book = new Book();
book.setType(type);
if (type.equalsIgnoreCase("novel")) {
book.setColor("white");
} else if (type.equalsIgnoreCase("SF")) {
book.setColor("black");
}
if (type.equalsIgnoreCase("novel")) {
book.setPages(200);
} else if (type.equalsIgnoreCase("SF")) {
book.setPages(150);
}
sendTo(address, book);
return book;
}
private static void prepareFor(String type) {
System.out.println(type + " 만들 준비 중");
}
private static void sendTo(String address, Book book) {
System.out.println(book.getType() + "을(를) 배송합니다.");
}
}
위의 코드의 문제점은 계속해서 type에 따라서 분기를 쳐야한다는 부분이다. 지금은 단 두 가지 타입이라 괜찮아보이지만, 책의 타입은 계속해서 추가될 수 있다. 만약에 타입이 10개, 100개까지 늘어나게된다면 코드는 어떻게 될까? 너무 길고 복잡해지지 않을까? 비즈니스 로직은 단순히 색깔을 칠하고 페이지 수를 정하는 것 밖에 없는데 코드가 몇천줄을 넘어갈 수도 있다.
TO-BE
public class Book {
private String type;
private String color;
private int pages;
//set, get 함수는 편의상 생략합니다.
}
public class NovelBook extends Book{
public NovelBook() {
setType("novel");
setColor("white");
setPages(200);
}
}
public class SFBook extends Book{
public SFBook() {
setType("SF");
setColor("black");
setPages(150);
}
}
일단 Book 클래스를 만들고, 이를 상속하는 NovelBook, SFBook을 만든다. 이를 통해, type 별로 분기를 쳐서 Book을 생성하는 것을 생성자로 깔끔하게 만들 수 있다.
public interface BookFactory {
default Book orderBook(String type, String address){
prepareFor(type);
Book book = createBook();
sendTo(address,book);
return book;
}
Book createBook();
private static void prepareFor(String type) {
System.out.println(type + " 만들 준비 중");
}
private static void sendTo(String address, Book book) {
System.out.println(book.getType() + "을(를) 배송합니다.");
}
}
public class NovelBookFactory implements BookFactory{
@Override
public Book createBook() {
return new NovelBook();
}
}
public class SFBookFactory implements BookFactory{
@Override
public Book createBook() {
return new SFBook();
}
}
그리고 기존 BookFactory에서 공통적으로 사용하던 함수는 static 함수로 만들고, 각 Book들을 생성하는 부분은 BookFactory 구현체를 통해 override한다.
이를 통해, 새로운 종류의 책이 추가되더라도 Book class를 상속하는 클래스 하나, BookFactory의 구현체 하나만 추가하면 된다. 즉, 기존 코드에는 아무런 영향이 없이 새로운 종류의 책을 추가할 수 있다는 것이다. 비즈니스 로직도 깔끔하게 유지할 수 있다.
팩토리 메소드 패턴의 장단점
팩토리 메소드 패턴의 장점으로는 ‘확장에는 열려있지만 변경에는 닫혀있다’는 점이다. 즉, 기존 코드를 건드리지 않고 확장이 가능하다. 팩토리와 인스턴스 사이의 관계를 느슨하게 유지할 수 있다. 단점으로는 클래스가 늘어난다는 점이다. (하지만, 개인적으로는 비즈니스 로직이 복잡해지고 when, if문이 무분별하게 사용되는 것 보다는 클래스가 늘어나는게 관리가 더 쉬울 것 같다고 생각된다.)
개인적인 느낀점
작년에 다양한 클래스들을 모아서 공통된 형태로 사용해야 하는 부분이 있었다. (원활한 설명을 돕기 위해 다양한 종류의 책 클래스들이었다고 해보자.) 그 당시에는 확장, 변경은 신경쓰지 못하고 각 책들이 가지고 있는 공통부분을 비롯한 모든 값들을 한 클래스에 모아놓고 Book이라고 묶었었다. 예를 들자면 제목, 페이지 수 등은 공통 부분이라 문제 없었지만, 외국 책이라면 번역가의 이름이 있을건데 이 또한 Book 클래스에 넣어놓고 nullable하게 취급했었다. 이 때문에 나중에 수정 및 확장이 너무 어려웠고, 당연히 위에 나타난 것 처럼 책 장르에 따라 when, if문들이 점점 늘어가면서 비즈니스 로직이 더렵혀졌었다 또한 nullable한 값들 때문에 if null 까지 신경 쓰는 등 코드가 너무 복잡해졌었다. 그 때, 팩토리 메소드 패턴을 활용해서 개선할 생각을 하지 못한게 아쉽다ㅠㅠ