A Trade goes through 3 state changes – placed, filled, and settled. Let’s solve this simple problem using both OOP & FP approaches.
Place –> Fill –> Settle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package com.shared; public class Trade { private final String name; private final int qty; public Trade(String name, int qty) { this.name = name; this.qty = qty; } public String getName() { return name; } public int getQty() { return qty; } @Override public String toString() { return "Trade [name=" + name + ", qty=" + qty + "]"; } } |
OOP approach by mutating the state
State changes are maintained via a variable named “status“, which is mutable. Here is the “OrderService.java”, which has both state & behavior.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package com.oop.eg1; import com.shared.Trade; public class OrderService { enum Status { INITIAL, FILLED, SETTLED }; private Trade trade; // mutable private Status status; // mutable public OrderService() {} public void place(Trade trade) { this.trade = trade; status = Status.INITIAL; System.out.println("Placed: " + trade); } public void fill() { if(trade == null) { throw new RuntimeException("Trade is null"); } status = Status.FILLED; System.out.println("Filled: " + trade); } public void settle() { if (status != Status.FILLED) { throw new RuntimeException("Order is not filled yet"); } status = Status.SETTLED; System.out.println("Settled: " + trade); } //getetrs & setters } |
Finally, let’s use the class with the main method that takes a “Trade” from initial to settled states.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.oop.eg1; import com.shared.Trade; public class OrderOOMain { public static void main(String[] args) { Trade xyz = new Trade("StockXYZ", 100); OrderService svc = new OrderService(); svc.place(xyz); svc.fill(); svc.settle(); } } |
Output:
1 2 3 4 5 | Placed: Trade [name=StockXYZ, qty=100] Filled: Trade [name=StockXYZ, qty=100] Settled: Trade [name=StockXYZ, qty=100] |
FP approach with type classes & composing functions
InitialOrder.java type class
1 2 3 4 5 6 7 8 9 10 11 12 | package com.fp.eg1; import com.shared.Trade; public class InitialOrder { public FillOrder execute(Trade trade) { System.out.println("Placed: " + trade); return new FillOrder(trade); } } |
FillOrder.java type class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.fp.eg1; import com.shared.Trade; public class FillOrder { private final Trade trade; //immutable public FillOrder(Trade trade) { this.trade = trade; } public SettleOrder execute() { System.out.println("Filled: " + trade); return new SettleOrder(trade); } } |
SettleOrder.java type class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.fp.eg1; import com.shared.Trade; public class SettleOrder { private final Trade trade; // immutable public SettleOrder(Trade trade) { this.trade = trade; } public SettleOrder execute() { System.out.println("Settled: " + trade); return this; } } |
Main class that composes the “execute” functions across the type classes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.fp.eg1; import java.util.function.Function; import com.shared.Trade; public class OrderFPMain { public static void main(String[] args) { Trade xyz = new Trade("StockXYZ", 100); InitialOrder initial = new InitialOrder(); //compose Function<Trade, SettleOrder> story = ((Function<Trade, FillOrder>)initial::execute) //returns a FillOrder .andThen(FillOrder::execute) //returns a SettleOrder .andThen(SettleOrder::execute); //returns a SettleOrder //evaluate story.apply(xyz); } } |
Output:
1 2 3 4 | Placed: Trade [name=StockXYZ, qty=100] Filled: Trade [name=StockXYZ, qty=100] Settled: Trade [name=StockXYZ, qty=100] |
Slightly modified version with a BiFunction
This takes an instance of the “InitialOrder” via the “apply” function of the “BiFunction”, which takes two inputs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.fp.eg1; import java.util.function.BiFunction; import com.shared.Trade; public class OrderFPMain { public static void main(String[] args) { Trade xyz = new Trade("StockXYZ", 100); //compose BiFunction<InitialOrder,Trade, SettleOrder> story = ((BiFunction<InitialOrder,Trade, FillOrder>)InitialOrder::execute) .andThen(FillOrder::execute) .andThen(SettleOrder::execute); //evaluate story.apply(new InitialOrder(),xyz); } } |
Output:
1 2 3 4 | Placed: Trade [name=StockXYZ, qty=100] Filled: Trade [name=StockXYZ, qty=100] Settled: Trade [name=StockXYZ, qty=100] |
Analyze the differences
1) The OOP mutates the states of the “OrderService”‘s status variable, whereas the FP approach creates a new object to capture the new state without mutating. OOP had to test the internal states and throw exceptions if invoked with incorrect internal states.
2) OOP focuses on the “nouns” as in the statuses being INITIAL, FILLED, SETTLED, etc whereas the FP focuses on the “verb” execute().
3) The FP composes the individual functions, which do their own functionality to create an output. Functions in FP are specialized in performing a single task, hence compose better than the OOP approach. For example, the above code can be composed in different ways to get the same outcome.
Approach 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import java.util.function.BiFunction; import java.util.function.Function; import com.shared.Trade; public class OrderFPMain { public static void main(String[] args) { Trade xyz = new Trade("StockXYZ", 100); // returns a FillOrder BiFunction<InitialOrder, Trade, FillOrder> placeOrder = ((BiFunction<InitialOrder, Trade, FillOrder>) InitialOrder::execute); FillOrder placedOrder = placeOrder.apply(new InitialOrder(), xyz); // returns a SettleOrder Function<FillOrder, SettleOrder> fillOrder = ((Function<FillOrder, SettleOrder>) FillOrder::execute); SettleOrder filledOrder = fillOrder.apply(placedOrder); // returns a SettleOrder Function<SettleOrder, SettleOrder> settleOrder = ((Function<SettleOrder, SettleOrder>) SettleOrder::execute); settleOrder.apply(filledOrder); } } |
Approach 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import java.util.function.BiFunction; import java.util.function.Function; import com.shared.Trade; public class OrderFPMain { public static void main(String[] args) { Trade xyz = new Trade("StockXYZ", 100); // returns a FillOrder BiFunction<InitialOrder, Trade, FillOrder> placeOrder = ((BiFunction<InitialOrder, Trade, FillOrder>) InitialOrder::execute); // returns a SettleOrder Function<FillOrder, SettleOrder> fillOrder = ((Function<FillOrder, SettleOrder>) FillOrder::execute); //start composing BiFunction<InitialOrder, Trade, SettleOrder> placeAndFill = placeOrder.andThen(fillOrder); SettleOrder filledOrder = placeAndFill.apply(new InitialOrder(), xyz); //settled filledOrder.execute(); } } |