팩토리 패턴?
'팩토리 패턴(factory pattern)'은 객체를 사용하는 코드에서 '객체 생성 부분'을 떼어내 '추상화'한 패턴이자, '상속 관계'에 있는 두 클래스에서 '상위 클래스'가 중요한 뼈대를 결정하고, '하위 클래스'에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴.
상위 클래스와 하위 클래스가 분리되기 때문에 '느슨한 결합'을 가지며, 상위 클래스에서는 인스턴스 생서 방식에 대해 전혀 알 필요가 없어 '더 많은 유연성'을 가지게 된다.
또한, 객체 생성 로직이 따로 있기 때문에 코드를 리팩토링하더라도 어느 한 곳만 고칠 수 있어 '유지 보수성이 증가'된다.
CPU 설계도, GPU 설계도라는 구체적인 내용이 들어있는 하위 클래스가 전달되고, 상위 클래스인 컴퓨터 공장에서 이 레시피들을 토대로 생산하는 생산 공정을 생각하면 이해하기 쉬워진다.
팩토리 패턴 - 자바 스크립트
const num = new Object(33)
const str = new Object('ccc')
num.constructor.name; // Number
str.constructor.name; // String
위 코드를 보면, 숫자나 문자열을 전달함에 따라 '다른 타입의 객체를 생성'한다.
즉, '전달받은 값'에 따라 '다른 객체를 생성'하며 '인스턴스의 타입'등을 정한다.
위에 말한 컴퓨터 공장을 예로 코드를 작성해보면 아래와 같다.
class CPU {
constructor() {
this.name = "cpu"
}
}
class GPU {
constructor() {
this.name = "gpu"
}
}
class CPUFactory {
static createComputer() {
return new CPU()
}
}
const factoryList = {CPUFactory, GPUFactory}
class ComputerFactory {
static createComputer(type) {
const factory = factoryList[type]
return factory.createComputer()
}
}
const main = () => {
// CPU 주문
const computer = ComputerFactory.createComputer("CPUFactory")
console.log(computer.name) // CPU
}
main()
위 코드를 보면, ComputerFactory라는 상위 클래스가 뼈대를 결정하고, 하위 클래스인 CPUFactory가 구체적인 내용을 결정한다.
이는 '의존성 주입'이라고도 볼 수 있다. ComputerFactory에서 CPUFactory의 인스턴스를 생성하는 것이 아니라, CPUFactory에서 생성한 인스턴스를 ComputerFactory에 주입하고 있기 때문이다.
또한, ComputerFactory 클래스를 잘 보면 static 키워드로 createComputer() 메서드를 정적 메서드로 선언한 것을 볼 수 있는데, 이렇듯 정적 메서드로 정의하면 클래스를 기반으로 객체를 만들지 않고 호출이 가능하며, 해당 메서드에 대한 '메모리 할당'을 '한 번만' 할 수 있는 장점도 있다.
팩토리 패턴 - 자바
팩토리 패턴을 자바로 구현하면 아래와 같이 할 수 있다.
abstract class Computer {
public abstreact int getPrice();
@Override
public String toString() {
return "my computher is" + this.getPrice();
}
}
class ComputerFactory {
public static Computer getComputer(String type, int price) {
if("CPU".equalsIgnoreCase(type)) return new CPU(price);
else if("GPU".equalsIgnoreCase(type)) return new GPU(price);
else {
return new DefaultComputer();
}
}
}
class DefaultComputer extends Computer {
private int price;
public DefaultComputer() {
this.price = -1;
}
@Override
public int getPrice() {
return this.price;
}
}
class CPU extends Computer {
private int price;
public CPU(int price) {
return this.price;
}
}
class GPU extends Computer {
private int price;
public GPU(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
public class HelloWorld {
public static void main(String[] args) {
Computer cpu = ComputerFactory.getComputer("CPU", 3000);
Computer gpu = ComputerFactory.getComputer("GPU", 3333);
}
]
위 코드를 보면 if("CPU".equalsIgnoreCase(type))을 통해 '문자열 비교 기반'으로 로직이 구성되었는데, 이를 Enum 또는 Map을 이용해서 매핑할 수도 있다.
※ Enum
: '상수의 집합을 정의'할 때 사용되는 타입이다. 월, 일, 색상 등을 담을 수 있다. 자바에서는 Enum이 다른 언어보다 많이 활용되고, 상수 뿐 아니라, 메서드를 집어넣어 관리할 수도 있다. Enum을 기반으로 상수 집합을 관리하면 코드를 리팩토링할 때 '상수 집합'에 대한 로직 수정 시 이 부분만 수정하면 되는 장점이 있고, 본질적으로 '스레드세이프(thread safe)' 하기 때문에 싱글톤 패턴을 만들 때도 도움이 된다.