팩토리 패턴은 객체 생성을 추상화하여 한 군데서 관리하는 패턴으로 상속 관계에 있는 두 객체에서 상위 객체가 추상화된 생성화 부분을, 하위 객체과 구체적인 구현을 담당한다.
상위 객체는 세부 구현 내용을 몰라도 되고 하위 객체에서 코드 수정이 생기면 그 부분만 수정하면 되기 때문에 두 객체의 의존성이 줄어든다.
이번 시간에는 이 Factory Pattern을 활용하여 객체 생성하는 부분을 함수형으로 프로그래밍해볼 것이다.
우선 지난 시간에 구현한 Car을 Abstract Class로 변경하고 각각 SUV, Sedan, Ven 객체를 구현한다.
public abstract class Car {
protected String name;
protected String brand;
public Car(String name, String brand) {
this.name = name;
this.brand = brand;
}
public abstract void drive();
}
public class Sedan extends Car{
public Sedan(String name, String brand) {
super(name, brand);
}
@Override
public void drive() {
System.out.println("SUV : " + name + " from " + brand);
}
}
public class SUV extends Car{
public SUV(String name, String brand) {
super(name, brand);
}
@Override
public void drive() {
System.out.println("SUV : " + name + " from " + brand);
}
}
public class Ven extends Car{
public Ven(String name, String brand) {
super(name, brand);
}
@Override
public void drive() {
System.out.println("SUV : " + name + " from " + brand);
}
}
이제 이 Car를 만드는 CarFactory를 만들어본다.
팩토리 클래스는 주로 Static method를 사용하며 차후 Car 종류가 추가됐을 때, 내부 코드 수정이 아니라 타입 추가하는 방식으로 만들기 위해 map을 사용한다.
public class CarFactory {
private static Map<String, BiFunction<String, String, Car>> cars;
static {
cars = new HashMap<>();
cars.put("suv", SUV::new);
cars.put("sedan", Sedan::new);
cars.put("ven", Ven::new);
}
public static Car create(String type, String name, String brand) {
try {
return cars.get(type).apply(name, brand);
} catch (NullPointerException e) {
throw new IllegalArgumentException("해당하는 차가 없습니다!");
}
}
}
그리고 다음과 같이 Input List가 주어지면 만들어진 Factory Class로 Car class 들을 만들 수 있다.
public class main {
public static void main(String[] args){
String [][] inputs = new String[][] {
{"ven", "Sienna", "Toyota"},
{"sedan", "Sonata", "Hyundai"},
{"sedan", "Model 5", "Tesla"},
{"suv", "Sorento", "KIA"}
};
List<Car> cars = new ArrayList<>();
for(String[] i : inputs){
cars.add(CarFactory.create(i[0], i[1], i[2]));
}
System.out.println(cars);
}
}
Map 함수는 ForEach 함수와 비슷하게 리스트와 함수를 파라미터로 받아 각 요소별로 함수를 수행한다.
여기서 ForEach 가장 중요한 차이점이 있는데 바로 수행한 결과를 배열에 담아서 반환한다.
ForEach는 단순히 수행만하고 끝이나지만 Map 함수는 수행한 결과값을 새로운 배열에 담아서 반환한다.
위 코드를 Map 함수를 구현해서 리팩토링해보겠다.
우선 Car Class에서 create 부분은 파라미터가 3개여서 TriFuction Interface를 만들거나 파라미터가 하나인 함수를 오버로딩해서 구현해야하는데 오버로딩 방법을 써서 새로운 create를 만든다.
public static Car create(String[] args) {
try {
return cars.get(args[0]).apply(args[1], args[2]);
} catch (NullPointerException e) {
throw new IllegalArgumentException("해당하는 차가 없습니다!");
}
}
Map 함수를 구현하면 아래와 같다.
public static <T> List<T> map(String[][] list, Function<String[], T> processor){
List<T> newList = new ArrayList<>();
for(String[] t : list){
newList.add(processor.apply(t));
}
return newList;
}
그 후, map 함수를 사용해서 기존 코드를 수정하면 아래와 같다.
public class main {
public static void main(String[] args){
String [][] inputs = new String[][] {
{"ven", "Sienna", "Toyota"},
{"sedan", "Sonata", "Hyundai"},
{"sedan", "Model 5", "Tesla"},
{"suv", "Sorento", "KIA"}
};
List<Car> cars = map(inputs, CarFactory::create);
System.out.println(cars);
}
public static <T> List<T> map(String[][] list, Function<String[], T> processor){
List<T> newList = new ArrayList<>();
for(String[] t : list){
newList.add(processor.apply(t));
}
return newList;
}
}