[디자인 패턴] 만두의 팩토리 메서드 패턴(Factory Method)
팩토리 메서드 패턴이란?
객체들을 생성하는 팩토리 인터페이스가 있고 인터페이스를 구현하는 클래스들이 객체 생성의 책임을 가진다.
자바를 공부하면서 디자인 패턴은 항상 뜬구름 잡는 소리처럼 들린다. 실제 코드에 디자인 패턴을 적용할 만한 로직이나 큰 프로그램 규모를 경험해보지 못해서 일지도 모른다. 이번 글에서는 내가 공부하며 이해한 팩토리 메서드 패턴에 대해서 설명해보려고 한다.
팩토리 인터페이스에는 하위 클래스들이 상속받아 구현해야 하는 생성 메서드가 정의되어 있다. 그리고 이 인터페이스를 하위 클래스들이 구현하여 실제 객체 생성은 하위 클래스들에서 담당한다. 인터페이스에서 정의하고 하위 클래스들에서 구현하여 생성해야 하기 때문에 팩토리 메서드에서 생성하는 객체들은 상속관계여야 사용 가능하다는 제약사항이 있다. 다만 이런 제약사항은 실제 팩토리 메서드를 사용할 때 다른 객체로 변경하고 싶으면 갈아 끼우기만 하면 되므로 장점으로 작용한다.
이러한 방식으로 구현되는 팩토리 메서드 패턴은 몇 가지 장점이 있다. 첫 번째로, 확장에는 열려있고 수정에는 닫혀있는 개방-폐쇄 원칙(OCP, Open-Closed Principle)을 지킬 수 있다. 만약 새로운 클래스를 추가하고 싶을 때에는 생성 책임을 지는 새로운 하위 클래스를 하나 더 만들면 되므로 기존 코드의 수정이 없다. 두 번째로, 단일 책임 원칙(single responsibility principle)을 지킬 수 있다. 펙토리 메서드 패턴에서는 객체를 사용하는 코드에서 객체를 생성하는 코드를 분리하여 유지 보수를 더 쉽게 만들어 준다. 세 번째로, 기존 객체를 재사용 할 수 있다. 생성 비용이 큰 객체의 경우 재사용하는 경우가 종종 있다. 정적 팩토리 메서드를 사용하면 기존에 생성한 객체를 재사용할 수 있어 리소스를 절약할 수 있다.
코드로 이해해보자
1. 클래스들을 정의한다.
Pet의 하위 타입인 Dog와 Cat을 정의하였다.
public interface Pet {
public void vocalize();
}
public class Cat implements Pet {
private String name;
public Cat(String name) {
this.name = name;
}
@Override
public void vocalize() {
System.out.println(name + ": meow ~");
}
}
public class Dog implements Pet {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public void vocalize() {
System.out.println(name + " : woof!!");
}
}
2. 팩토리 메서드
이제 Pet 객체를 생성하는 팩토리 메서드들을 PetShop으로 정의해 보자.
여기서는 매우 작은 예제로 만들어서 코드가 모두 같지만 실제로는 클래스에 의존적인 로직을 넣어도 된다. 하위 클래스들인 DogShop과 CatShop에서 타입을 변경하는 방식으로 간접적으로 로직을 변경하였다.
public interface PetShop {
public Pet sell(String name);
}
public class DogShop implements PetShop {
@Override
public Pet sell(String name) {
return new Dog(name);
}
}
public class CatShop implements PetShop {
@Override
public Pet sell(String name) {
return new Cat(name);
}
}
3. 새로운 클래스 추가하기
새로운 타입을 추가하기 위해서 해야 할 일은 간단하다. 새로운 타입이 추가되었다면 새로운 하위 클래스를 정의하면 된다.
4. 사용해 보기
입력한 문자열에 따라 분기 처리하여 예제를 테스트해보았다.
public class Application {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
PetShop shop = null;
switch (input) {
case "bird" -> shop = new BirdShop();
case "cat" -> shop = new CatShop();
case "dog" -> shop = new DogShop();
default -> throw new IllegalArgumentException("invalid input");
}
shop.sell("pet").vocalize();
}
}
글을 마치며
팩토리 메서드 패턴에 대해서 알아보았다. 이전에 부트캠프에서 과제로 나왔던 문제를 해결할 때 단순히 상속과 클래스로 모든 것을 해결하려고 했었다. 클래스가 너무 많고, 코드는 중복이고, 분기는 여러 곳에서 처리되는 매우 꼬여있는 코드가 만들어져 해결책을 고민하고 있었다. 그때 피드백으로 받은 Simple factory pattern으로 개선한 코드가 인상 깊었다. 깔끔했고 타입들을 효율적으로 유지보수 할 수 있었다.
디자인 패턴에 코드를 끼워 맞추는 것은 좋지 않지만, 잘 기억해 뒀다가 이후 코드에 적용하여 더 나은 코드를 작성할 수 있으면 좋겠다.
Reference
팩토리 메서드 패턴
/ 디자인 패턴들 / 생성 패턴 팩토리 메서드 패턴 다음 이름으로도 불립니다: 가상 생성자, Factory Method 의도 팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지
refactoring.guru