DSL이 파라미터의 위치에 따라 역할이 정해지는 중첩된 함수(Nested Function) 패턴으로 구현된 DSL의 단점은 Lambda(함수형인터페이스)를 활용해 개선할 수 있다.
NestedFunction + Lambda를 활용한 파라미터 역할 지정
기본형 파라미터로 입력받던 quantity와 price를 대체하기 위한 Supplier를 정의
public class SimpleSupplier<T> implements Supplier<T> {
final T value;
protected SimpleSupplier(final T value) {
this.value = value;
}
private SimpleSupplier() {
value = null;
}
@Override
public T get() {
return value;
}
}
Quantity와 Price 클래스 생성
public class Quantity<Integer> extends SimpleSupplier {
protected Quantity(Integer value) {
super(value);
}
}
public class Price<Double> extends SimpleSupplier {
protected Price(Double value) {
super(value);
}
}
- 객체를 직접 생성하는 가능성을 없애기 위해 생성자를 Protected로 외부에서 생성 차단
- Quantity<Integer>, Price<Double>의 중복되는 코드를 부모클래스(SimpleSupplier)의 제네릭<T>를 활용해 정리
public class NestedFunctionOrderBuilderWithLambda {
public static Order order(String customer, Trade... trades) {
Order order = new Order();
order.setCustomer(customer);
Stream.of(trades).forEach(order::addTrade); //주문에 모든 거래 추가
return order;
}
public static Trade buy(Quantity quantity, Stock stock, Price price) {
return buildTrade(quantity, stock, price, Trade.Type.BUY);
}
public static Trade sell(Quantity quantity, Stock stock, Price price) {
return buildTrade(quantity, stock, price, Trade.Type.SELL);
}
private static Trade buildTrade(Supplier<Integer> quantity, Stock stock, Supplier<Double> price, Trade.Type type) {
Trade trade = new Trade();
trade.setQuantity(quantity.get());
trade.setType(type);
trade.setStock(stock);
trade.setPrice(price.get());
return trade;
}
public static Stock stock(String symbol, String market) {
Stock stock = new Stock();
stock.setSymbol(symbol);
stock.setMarket(market);
return stock;
}
public static Price<Double> price(Double price) {
return new Price(price);
}
public static Quantity<Integer> quantity(Integer quantity) {
return new Quantity(quantity);
}
}
- buy(), sell()에 사용되던 integer, double 파라미터라 각각 Quantity, Price 로 변경되었다.
- Quantity, Price는 Supplier<T>를 상속받아 만들었기 때문에 함수형인터페이스이며, 객체가 아닌 메서드 참조로 적용된다.
- Quantity, Price 클래스의 생성자는 protected로 직접 생성이 불가능하며, 각각 quantity(), price() static 팩토리 메서드를 통해 만들 수 있다. 즉, 파라미터 주입 방식을 한가지로 제한한다.
- 숫자로 인자를 넣는 방식보다 언어(DSL)이 명확해 진다. 즉, 비개발자인 도메인 전문가도 언어를 이해하기 쉬워진다.
- 객체를 생성하는 방식을 static 팩토리 메서드로 제한함으로서 의도하지 않은 코드가 생성되는 것을 방지한다.
After 사용 예
@Test
void 중첩함수_람다이용_명확화() {
Order order = order("myCustomer",
buy(quantity(100), stock("IBM", "NYSE"), price(125.00)),
sell(quantity(50), stock("GOOGLE", "NASDAQ"), price(375.00))
);
System.out.println(order.getValue());
}
Before 비교
@Test
void 중첩함수이용_간소화() {
Order order = order("myCustomer",
buy(80, stock("IBM", "NYSE"), 125.00),
sell(50, stock("GOOGLE", "NASDAQ"), 375.00)
);
System.out.println(order.getValue());
}
[Question]
- Stock은 함수형인터페이스를 사용하지 않는데, Price, Quantity는 왜 복잡하게 함수형 인터페이스를 사용하나?
- Stock은 객체를 넣는 방식인데, double, integer는 기본형으로 DSL에서 값의 이해가 어려움
- double, integer를 메서드로 생성해도 결국 입력 파라미터가 기본형으로 받기 때문에 메서드 사용을 강제하지 못함
- 동일한 리턴값을 갖는 메서드가 여러 개 있는 경우 어느 메서드를 사용해야 하는지 알 수 없음
[장단점]
- 단점 - 함수형 인터페이스로 메서드 참조를 제공하기 위해 추가되는 클래스와 로직이 늘어남
- 장점 - 구현이 복잡해지는 대신에 사용 방식이 명확해지는 것이 장점
'프로그래밍 > OOP_Pattern_TDD' 카테고리의 다른 글
Flowable.crete 구조 파악 (0) | 2023.07.17 |
---|---|
RxJava Single 구조 파해치기 (0) | 2023.04.18 |
자바 DSL - 중첩된 함수(NestedFunction) 패턴 (0) | 2023.03.08 |
자바 DSL - 메서드 체인 패턴 (0) | 2023.03.08 |
람다 - 팩토리 패턴 (0) | 2023.03.03 |