Thỉnh thoảng bạn có đọc đâu đó về Fluent API,  hay syntatic sugar coding nhưng không hiểu rõ nó là cái gì. Trong bài viết này tôi sẽ demo cách viết Fluent API. Bạn đọc xong sẽ thích dùng và áp dụng được ngay

 Đây là ví dụ của một lệnh gọi Fluent

new Order("Cuong", "0902209011", "Tầng 12A, Viwaseen Tower, 48 Tố Hữu")
      .addItem(new OrderItem("Pizza cá ngừ", 200000, 1))
      .addItem(new OrderItem("Pizza nấm", 180000, 2))
      .addItem(new OrderItem("Pizza bò Úc", 190000, 1))
      .passToKitchen()
      .calculateTotalMount()
      .printInvoice()
      .ship()
      .close();

Hay ví dụ Java Stream thường có những lệnh kiểu thế này

totalAmount = orderItems
    .stream()
    .mapToInt(item -> item.amount * item.unitPrice)
    .sum();

Giờ chúng ta bắt đầu lập trình phong cách Fluent API nhé.

1. Ví dụ về xử lý đơn hàng Pizza

Vòng đời một đơn hàng Pizza bắt đầu như sau:

  1. Nhập đơn hàng (Order) gồm có: tên khách hàng, số di động, địa chỉ giao hàng
  2. Nhập từng mục trong đơn hàng (Order Item): tên món, số lượng và giá đơn vị
  3. Chuyển đơn hàng cho nhà bếp xử lý
  4. Tính tổng số tiền phải thu
  5. In hoá đơn
  6. Chuyển hàng
  7. Đóng đơn hàng

Nếu viết theo phong cách cổ điển bạn sẽ tạo đối tượng Order, rồi lần luợt gọi các phương thức kiểu như thế này

Order order = new Order("Cuong", "0902209011", "Tầng 12A, Viwaseen Tower, 48 Tố Hữu");

order.addItem(new OrderItem("Pizza cá ngừ", 200000, 1))
order.addItem(new OrderItem("Pizza nấm", 180000, 2))
order.addItem(new OrderItem("Pizza bò Úc", 190000, 1))
order.passToKitchen()
order.calculateTotalMount()
order.printInvoice()
order.ship()
order.close();

Bạn có thấy đối tượng order lặp lại 9 lần không? Viết cũng mỏi tay.

2. Khai báo class OrderItem và Order

Ở phần này tôi vẫn khai báo class như bình thường

import java.util.ArrayList;

class OrderItem {
  String item;
  int unitPrice;
  int amount;  

  public OrderItem(String item, int unitPrice, int amount) {
    this.item = item;
    this.unitPrice = unitPrice;
    this.amount = amount;
  }
}

class Order {
  String customerName;
  String customerMobile;
  String shippingAddress;
  ArrayList<OrderItem> orderItems;
  int totalAmount;

  public Order(String customerName, String customerMobile, String shippingAddress) {
    this.customerName = customerName;
    this.customerMobile = customerMobile;
    this.shippingAddress = shippingAddress;
    orderItems = new ArrayList<>();
    totalAmount = 0;
  }
}

3. Viết kiểu Fluent, method sẽ trả về chính đối tượng

Bản chất của một Fluent method là trả về chính đối tượng đó, để từ đó nối chuỗi các phương thức tiếp theo.

Thêm một mục vào đơn hàng

public Order addItem(OrderItem item) {
  orderItems.add(item);
  System.out.println(orderItems.size() + " . Sub total = " + item.amount * item.unitPrice);
  return this;  //Trả về chính đối tượng đó
}

Chuyển order cho nhà bếp

public Order passToKitchen() {
  System.out.println("Pass this order to kitchen");
  return this;
}

Tính tổng số tiền khách hàng phải trả. Ở đây tôi giới thiệu 2 cách tính tổng:

  1. Cách cổ điển dùng vòng lặp for
  2. Cách dùng Java stream. Bạn có thấy cú pháp Java stream cũng là fluent đó.
public Order calculateTotalMount() {
  /*for (OrderItem orderItem : orderItems) {
    totalAmount += orderItem.amount * orderItem.unitPrice;
  }*/

  totalAmount = orderItems
  .stream()
  .mapToInt(item -> item.amount * item.unitPrice)
  .sum();

  return this;
}

vân vân và vân vân. Bạn có thể viết bao nhiều method kiểu Fluent  cũng được.

4. Ưu nhược điểm của Fluent API

Ưu điểm

  1. Giúp lập trình viên viết code gọn hơn
  2. Code sẽ gần giống với ngôn ngữ đời thường hơn. Rất hữu ích khi viết unit test phong cách Gherkin Syntax.

Nhược điểm

  1. Khó debug
  2. Khó viết try catch để bắt lỗi
  3. Chỉ phù hợp với phương thức không trả về dữ liệu, thì ta tranh thủ trả về chính đối tượng đó.

5. Toàn bộ code demo tôi viết

file DemoFluent.java

import java.util.ArrayList;

class OrderItem {
  String item;
  int unitPrice;
  int amount;  

  public OrderItem(String item, int unitPrice, int amount) {
    this.item = item;
    this.unitPrice = unitPrice;
    this.amount = amount;
  }
}

class Order {
  String customerName;
  String customerMobile;
  String shippingAddress;
  ArrayList<OrderItem> orderItems;
  int totalAmount;

  public Order(String customerName, String customerMobile, String shippingAddress) {
    this.customerName = customerName;
    this.customerMobile = customerMobile;
    this.shippingAddress = shippingAddress;
    orderItems = new ArrayList<>();
    totalAmount = 0;
  }
public Order addItem(OrderItem item) {
  orderItems.add(item);
  System.out.println(orderItems.size() + " . Sub total = " + item.amount * item.unitPrice);
  return this;
}

  public Order passToKitchen() {
    System.out.println("Pass this order to kitchen");
    return this;
  }

  public Order calculateTotalMount() {
    /*for (OrderItem orderItem : orderItems) {
      totalAmount += orderItem.amount * orderItem.unitPrice;
    }*/

    totalAmount = orderItems
    .stream()
    .mapToInt(item -> item.amount * item.unitPrice)
    .sum();

    return this;
  }

  public Order printInvoice() {
    System.out.println("Total amount is " + totalAmount);
    return this;
  }

  public Order ship() {
    System.out.println("Ship this order to " + customerName 
    + " with mobile " + customerMobile 
    + " at " + shippingAddress);
    return this;
  }

  public void close() {
    System.out.println("Order complete");  
  }
}

public class DemoFluent {
  public static void main(String[] args) {
    new Order("Cuong", "0902209011", "Tầng 12A, Viwaseen Tower, 48 Tố Hữu")
      .addItem(new OrderItem("Pizza cá ngừ", 200000, 1))
      .addItem(new OrderItem("Pizza nấm", 180000, 2))
      .addItem(new OrderItem("Pizza bò Úc", 190000, 1))
      .passToKitchen()
      .calculateTotalMount()
      .printInvoice()
      .ship()
      .close();
  }
}

Kết quả màn hình sẽ in ra thế này

1 . Sub total = 200000
2 . Sub total = 360000
3 . Sub total = 190000
Pass this order to kitchen
Total amount is 750000
Ship this order to Cuong with mobile 0902209011 at cuong@techmaster.vn
Order complete