package com.bookshop.creational.builder;
import com.bookshop.domain.Book;
import com.bookshop.domain.Customer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* A customer order with two required fields and several optional ones -- the classic
* situation where telescoping constructors become unreadable and a builder shines.
*/
public final class Order {
private final Customer customer;
private final List<Book> books;
private final String giftMessage;
private final String deliveryInstructions;
private final String promoCode;
private final boolean expressDelivery;
private Order(Builder builder) {
this.customer = builder.customer;
this.books = List.copyOf(builder.books);
this.giftMessage = builder.giftMessage;
this.deliveryInstructions = builder.deliveryInstructions;
this.promoCode = builder.promoCode;
this.expressDelivery = builder.expressDelivery;
}
/** Entry point: {@code Order.forCustomer(alice).add(book).express().build()}. */
public static Builder forCustomer(Customer customer) {
return new Builder(customer);
}
public Customer customer() {
return customer;
}
public List<Book> books() {
return books;
}
public Optional<String> giftMessage() {
return Optional.ofNullable(giftMessage);
}
public Optional<String> deliveryInstructions() {
return Optional.ofNullable(deliveryInstructions);
}
public Optional<String> promoCode() {
return Optional.ofNullable(promoCode);
}
public boolean expressDelivery() {
return expressDelivery;
}
public BigDecimal subtotal() {
return books.stream().map(Book::price).reduce(BigDecimal.ZERO, BigDecimal::add);
}
public static final class Builder {
private final Customer customer;
private final List<Book> books = new ArrayList<>();
private String giftMessage;
private String deliveryInstructions;
private String promoCode;
private boolean expressDelivery;
private Builder(Customer customer) {
this.customer = Objects.requireNonNull(customer, "customer");
}
public Builder add(Book book) {
books.add(Objects.requireNonNull(book, "book"));
return this;
}
public Builder giftMessage(String message) {
this.giftMessage = message;
return this;
}
public Builder deliveryInstructions(String instructions) {
this.deliveryInstructions = instructions;
return this;
}
public Builder promoCode(String code) {
this.promoCode = code;
return this;
}
public Builder express() {
this.expressDelivery = true;
return this;
}
/** Validation lives in one place, and the resulting {@link Order} is immutable. */
public Order build() {
if (books.isEmpty()) {
throw new IllegalStateException("an order must contain at least one book");
}
return new Order(this);
}
}
}