Justin의 개발 로그
article thumbnail

주식(stock)을 거래(trade)를 하는 주문(order) 기능을 Java로 구현

public class Order {
    private String customer;
    private List<Trade> trades = new ArrayList<>();

    public void addTrade(Trade trade) {
        trades.add(trade);
    }

    public String getCustomer() {
        return customer;
    }

    public void setCustomer(final String customer) {
        this.customer = customer;
    }

    public double getValue() {
        return trades.stream().mapToDouble(Trade::getValue).sum();
    }
}
public class Trade {
    public enum Type {BUY, SELL}

    private Type type;
    private Stock stock;
    private int quantity;
    private double price;

    public Type getType() {
        return type;
    }

    public void setType(final Type type) {
        this.type = type;
    }

    public Stock getStock() {
        return stock;
    }

    public void setStock(final Stock stock) {
        this.stock = stock;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(final int quantity) {
        this.quantity = quantity;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(final double price) {
        this.price = price;
    }

    public double getValue() {
        return quantity * price;
    }
}
public class Stock {
    private String symbol;
    private String market;

    public String getSymbol() {
        return symbol;
    }

    public void setSymbol(final String symbol) {
        this.symbol = symbol;
    }

    public String getMarket() {
        return market;
    }

    public void setMarket(final String market) {
        this.market = market;
    }

}
public class TradeStockTest {
    @Test
    void 도메인객체를이용한주식거래() {
        Order order = new Order();
        order.setCustomer("myCustomer");


        Stock stock1 = new Stock();
        stock1.setSymbol("IBM");
        stock1.setMarket("NYSE");

        Trade trade1 = new Trade();
        trade1.setType(Trade.Type.BUY);
        trade1.setStock(stock1);
        trade1.setPrice(125.00);
        trade1.setQuantity(80);

        order.addTrade(trade1);



        Stock stock2 = new Stock();
        stock2.setSymbol("GOOGLE");
        stock2.setMarket("NASDAQ");

        Trade trade2 = new Trade();
        trade2.setType(Trade.Type.BUY);
        trade2.setStock(stock2);
        trade2.setPrice(375.00);
        trade2.setQuantity(50);

        order.addTrade(trade2);
    }
}
  • 위 코드는 장황하며, 도메인 전문가(비개발자, 주식 전문가)가 위 코드를 이해하고 검증하기가 어렵다. 
  • 도메인 전문가도 코드를 쉽게 이해할 수 있도록 DSL(Domain Specific Languages)을 제공하면 비개발자도 코드를 검증하기 쉬워진다.

메서드 체인 패턴을 이용한 DSL 구현

  • orderBuilder의 forCustomer 메서드를 시작으로, Buy||Sell -> stock -> on -> at 으로 메서드가 체이닝 되어 있다.
  • 체인의 연결은 각 Builder 클래스를 통해 연결된다.  
  • 각 체인은 최초의 빌더인 orderBuilder를 파라미터 참조를 통해 전달한다.
  • 체인의 끝에서는 파라미터 참조된 orderBuilder의 최종 메서드로 연결된다.
  • 체인의 중간 단계에서는 값설정 또는 처리되어야 하는 로직이 포함된다. 
  • 메서드 체인 패턴의 장점은 호출의 순서를 강제하기 때문에 메서드 호출이 잘못될 가능성을 없앨 수 있다.

 

메서드 체인 패턴으로 개발된 DSL을 사용한 주식거래

public class MethodChainingTest {
    @Test
    void 메서드체인_트레이드_코스() {
        Order order = MethodChainingOrderBuilder.forCustomer("BigBank")
                .buy(80)
                .stock("IBM")
                .on("NYSE")
                .at(125.00)
                .sell(50)
                .stock("GOOGLE")
                .on("NASDAQ")
                .at(375.00)
                .end();

        System.out.println(order.getValue());
    }
}
public class MethodChainingOrderBuilder {
    public final Order order = new Order();

    private MethodChainingOrderBuilder(String customer) {
        order.setCustomer(customer);
    }

    // 빌더 패턴
    public static MethodChainingOrderBuilder forCustomer(String customer) {
        return new MethodChainingOrderBuilder(customer);
    }

    public TradeBuilder buy(int quantity) {
        return new TradeBuilder(this, Trade.Type.BUY, quantity);
    }

    public TradeBuilder sell(int quantity) {
        return new TradeBuilder(this, Trade.Type.SELL, quantity);
    }

    // 빌더 패턴
    public MethodChainingOrderBuilder addTrade(Trade trade) {
        order.addTrade(trade);
        return this;
    }

    // 주문 만들기를 종료하고 반환
    public Order end() {
        return order;
    }
}
public class TradeBuilder {
    private final MethodChainingOrderBuilder builder;
    public final Trade trade = new Trade();

    public TradeBuilder(MethodChainingOrderBuilder builder, Trade.Type type, int quantity) {
        this.builder = builder;
        trade.setType(type);
        trade.setQuantity(quantity);
    }

    public StockBuilder stock(String symbol) {
        return new StockBuilder(builder, trade, symbol);
    }
}
public class StockBuilder {
    private final MethodChainingOrderBuilder builder;
    private final Trade trade;
    private final Stock stock = new Stock();

    public StockBuilder(MethodChainingOrderBuilder builder, Trade trade, String symbol) {
        this.builder = builder;
        this.trade = trade;
        stock.setSymbol(symbol);
    }

    public TradeBuilderWithStock on(String market) {
        stock.setMarket(market);
        trade.setStock(stock);
        return new TradeBuilderWithStock(builder, trade);
    }
}
public class TradeBuilderWithStock {
    private final MethodChainingOrderBuilder builder;
    private final Trade trade;

    public TradeBuilderWithStock(MethodChainingOrderBuilder builder, Trade trade) {
        this.builder = builder;
        this.trade = trade;
    }

    public MethodChainingOrderBuilder at(double price) {
        trade.setPrice(price);
        return builder.addTrade(trade);
    }
}

[단점]

  • 빌더를 구현해야 한다는 것이 메서드 체인의 단점이다. 상위 빌더를 하위 빌더와 연결하기 위한 코드가 많이 필요하다. 
  • 도메인 객체의 구조에 맞춰 들여쓰기를 제공하지 못하는 것도 단점이다. 
//아래 코드는 이해를 돕기위해 인위적으로 들여쓰기를 했을 뿐 제공된 DSL은 들여쓰기의 기준을 제시하지 못함
public class MethodChainingTest {
    @Test
    void 메서드체인_트레이드_코스() {
        Order order = MethodChainingOrderBuilder.forCustomer("BigBank")
                .buy(80)
                    .stock("IBM")
                    .on("NYSE")
                    .at(125.00)
                .sell(50)
                    .stock("GOOGLE")
                    .on("NASDAQ")
                    .at(375.00)
                .end();

        System.out.println(order.getValue());
    }
}
profile

Justin의 개발 로그

@라이프노트

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!