1. Solid Principle

1.1 Single Responsibility Principle

A class should have only one reason to change, meaning each class should focus on a single functionality or responsibility.

SHOW CODE
import java.io.IOException;
import java.util.*;
import java.util.regex.*;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 1. User class is only used to represent entity.
 * 2. UserValidator class is solely responsible for validating user objects.
 * 3. Store class is only used to manage user objects storage.
 * 4. UserPersistenceService class is dedicated to save the user data.
 * 5. UserController is solely used to handle incoming requests (in this case, creating users form JSON input).
 */

/*
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.18.0</version>
</dependency>
 */

/**
 * Represents a user with basic details.
 * User class is only used to represent entity.
 */
class User {
    private String name;
    private String email;
    private String address;

    public User() {}
    public User(String name, String email, String address) {
        this.name = name;
        this.email = email;
        this.address = address;
    }

    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }

    @Override
    public String toString() {
        return "User [name=" + name + ", email=" + email + ", address=" + address + "]";
    }
}

/**
 * Handles incoming JSON requests that work on User.
 * UserController is solely used to handle incoming requests (in this case, creating users form JSON input).
 */
class UserController {
    private final UserPersistenceService persistenceService = new UserPersistenceService();

    public String createUser(String userJson) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        User user = mapper.readValue(userJson, User.class);

        UserValidator validator = new UserValidator();
        if (!validator.validateUser(user)) {
            return "ERROR";
        }

        persistenceService.saveUser(user);
        return "SUCCESS";
    }
}

/**
 * Handles persistence of User objects.
 * UserPersistenceService class is dedicated to save the user data.
 */
class UserPersistenceService {
    private final Store store = new Store();

    public void saveUser(User user) {
        store.store(user);
    }
}

/**
 * Validates User data.
 * UserValidator class is solely responsible for validating user objects.
 */
class UserValidator {
    public boolean validateUser(User user) {
        if (!isPresent(user.getName()) || !isValidAlphaNumeric(user.getName())) {
            return false;
        }
        user.setName(user.getName().trim());
        user.setEmail(user.getEmail().trim());
        return isValidEmail(user.getEmail());
    }

    private boolean isPresent(String value) {
        return value != null && value.trim().length() > 0;
    }

    private boolean isValidAlphaNumeric(String value) {
        Pattern pattern = Pattern.compile("[^A-Za-z0-9]");
        Matcher matcher = pattern.matcher(value);
        return !matcher.find();
    }

    private boolean isValidEmail(String value) {
        Pattern pattern = Pattern.compile("^[\\w-\\.]+@[\\w-]+(\\.[\\w-]+)+$");
        Matcher matcher = pattern.matcher(value);
        return matcher.find();
    }
}

/**
 * Stores User data in memory.
 * Store class is only used to manage user objects storage.
 */
class Store {
    private static final Map<String, User> STORAGE = new HashMap<>();

    public void store(User user) {
        synchronized (STORAGE) {
            STORAGE.put(user.getName(), user);
        }
    }

    public User getUser(String name) {
        synchronized (STORAGE) {
            return STORAGE.get(name);
        }
    }
}

/**
 * Main class for demonstrating the Single Responsibility Principle.
 */
public class Main {
    private static final String VALID_USER_JSON = "{\"name\": \"Signal\", \"email\": \"signalyu999@gmail.com\", \"address\":\"999 Sugar lane\"}";
    private static final String INVALID_USER_JSON = "{\"name\": \"Sam\", \"email\": \"sam@email\", \"address\":\"111 Sugar lane\"}";

    public static void main(String[] args) throws IOException {
        UserController controller = new UserController();

        String response = controller.createUser(VALID_USER_JSON);
        System.out.println("Valid JSON response: " + response);

        response = controller.createUser(INVALID_USER_JSON);
        System.out.println("Invalid JSON response: " + response);
    }
}

Each class in the above code strictly adheres to the Single Responsibility Principle by focusing on a specific responsibility:

  • User for representing the user entity
  • UserController for request handling
  • UserPersistenceService for data persistence
  • UserValidator for user data validation
  • Store for managing in-memory data storage
---
title: Single Responsibility Principle - Class Diagram
---
classDiagram
    class User {
        - String name
        - String email
        - String address
        + String toString()
    }

    class Store {
        + void store(User user)
        + User getUser(String name)
    }

    class UserPersistenceService {
        + void saveUser(User user)
    }

    class UserValidator {
        - boolean isPresent(String value)
        - boolean isValidAlphaNumeric(String value)
        - boolean isValidEmail(String value)
        + boolean validateUser(User user)
    }

    class UserController {
        + String createUser(String userJson)
    }

    class Main {
        + void main(String[] args)
    }

    User --* Store
    Store --* UserPersistenceService
    UserPersistenceService  --* UserController
    UserController --> UserValidator : create
    Main --> UserController : create

1.2 Open-Closed Principle

A software entity (class, module, function, etc.) should be open for extension but closed for modification.

点击查看代码
import java.time.LocalDateTime;
import java.util.*;

// Abstract base class, closed for modification but open for extension
abstract class Subscriber {

    protected Long subscriberId;
    protected String address;
    protected Long phoneNumber;
    protected int baseRate;

    // Getter and Setter methods
    public Long getSubscriberId() {
        return subscriberId;
    }

    public void setSubscriberId(Long subscriberId) {
        this.subscriberId = subscriberId;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Long getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(Long phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public int getBaseRate() {
        return baseRate;
    }

    public void setBaseRate(int baseRate) {
        this.baseRate = baseRate;
    }

    // Abstract method to calculate the bill; implementation is defined in subclasses
    public abstract double calculateBill();
}

// PhoneSubscriber class extends Subscriber, calculates the phone bill
class PhoneSubscriber extends Subscriber {

    @Override
    public double calculateBill() {
        List<CallHistory.Call> calls = CallHistory.getCurrentCalls(subscriberId);
        long totalDuration = calls.stream().mapToLong(CallHistory.Call::getDuration).sum();
        return (totalDuration * baseRate) / 100.0;
    }
}

// ISPSubscriber class extends Subscriber, calculates the internet bill
class ISPSubscriber extends Subscriber {

    private long freeUsage; // Free usage limit in data units

    @Override
    public double calculateBill() {
        List<InternetSessionHistory.InternetSession> sessions = InternetSessionHistory.getCurrentSessions(subscriberId);
        long totalData = sessions.stream().mapToLong(InternetSessionHistory.InternetSession::getDataUsed).sum();
        long chargeableData = totalData - freeUsage;

        return chargeableData <= 0 ? 0 : (chargeableData * baseRate) / 100.0;
    }

    public long getFreeUsage() {
        return freeUsage;
    }

    public void setFreeUsage(long freeUsage) {
        this.freeUsage = freeUsage;
    }
}

// InternetSessionHistory class to manage internet usage records
class InternetSessionHistory {

    public static class InternetSession {
        private final LocalDateTime begin;
        private final Long subscriberId;
        private final Long dataUsed;

        public InternetSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
            this.subscriberId = subscriberId;
            this.begin = begin;
            this.dataUsed = dataUsed;
        }

        public LocalDateTime getBegin() {
            return begin;
        }

        public long getDataUsed() {
            return dataUsed;
        }

        public Long getSubscriberId() {
            return subscriberId;
        }
    }

    private static final Map<Long, List<InternetSession>> SESSIONS = new HashMap<>();

    // Retrieves the current sessions for a subscriber, returns an empty list if none found
    public static synchronized List<InternetSession> getCurrentSessions(Long subscriberId) {
        return SESSIONS.getOrDefault(subscriberId, Collections.emptyList());
    }

    // Adds a new internet session record for a subscriber
    public static synchronized void addSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
        SESSIONS.computeIfAbsent(subscriberId, k -> new LinkedList<>()).add(new InternetSession(subscriberId, begin, dataUsed));
    }
}

// CallHistory class to manage call records
class CallHistory {

    public static class Call {
        private final LocalDateTime begin;
        private final long duration;
        private final Long subscriberId;

        public Call(Long subscriberId, LocalDateTime begin, long duration) {
            this.subscriberId = subscriberId;
            this.begin = begin;
            this.duration = duration;
        }

        public LocalDateTime getBegin() {
            return begin;
        }

        public long getDuration() {
            return duration;
        }

        public Long getSubscriberId() {
            return subscriberId;
        }
    }

    private static final Map<Long, List<Call> CALLS = new HashMap<>();

    // Retrieves the current calls for a subscriber, returns an empty list if none found
    public static synchronized List<Call> getCurrentCalls(Long subscriberId) {
        return CALLS.getOrDefault(subscriberId, Collections.emptyList());
    }

    // Adds a new call record for a subscriber
    public static synchronized void addSession(Long subscriberId, LocalDateTime begin, long duration) {
        CALLS.computeIfAbsent(subscriberId, k -> new LinkedList<>()).add(new Call(subscriberId, begin, duration));
    }
}

public class Main {

    public static void main(String[] args) {
        // Initialize PhoneSubscriber and ISPSubscriber with sample data
        PhoneSubscriber phoneSubscriber = new PhoneSubscriber();
        phoneSubscriber.setSubscriberId(1L);
        phoneSubscriber.setBaseRate(10);

        ISPSubscriber ispSubscriber = new ISPSubscriber();
        ispSubscriber.setSubscriberId(2L);
        ispSubscriber.setBaseRate(5);
        ispSubscriber.setFreeUsage(1000);

        // Add call records for PhoneSubscriber
        CallHistory.addSession(1L, LocalDateTime.now().minusMinutes(5), 300); // 300 seconds call
        CallHistory.addSession(1L, LocalDateTime.now().minusMinutes(10), 600); // 600 seconds call

        // Calculate and print the phone subscriber's bill
        double phoneBill = phoneSubscriber.calculateBill();
        System.out.println("Phone Subscriber's Bill: " + phoneBill);

        // Add internet session records for ISPSubscriber
        InternetSessionHistory.addSession(2L, LocalDateTime.now().minusHours(1), 500); // 500 data units
        InternetSessionHistory.addSession(2L, LocalDateTime.now().minusHours(2), 800); // 800 data units

        // Calculate and print the ISP subscriber's bill
        double ispBill = ispSubscriber.calculateBill();
        System.out.println("ISP Subscriber's Bill: " + ispBill);
    }
}

The above code demonstrates the Open/Closed Principle (OCP) by separating the base Subscriber class from its specific implementations, PhoneSubscriber and ISPSubscriber, allowing for new functionalities to be added without modifying the existing code structure.

  1. Closed for Modification: The Subscriber base class is closed for modification. It defines common properties (like subscriberId, address, and baseRate) and an abstract calculateBill method that acts as a placeholder for billing logic. This base class remains unchanged even as new types of subscribers or billing strategies are introduced.

  2. Open for Extension: The calculateBill method in Subscriber can be extended by subclasses, like PhoneSubscriber and ISPSubscriber, to implement custom billing calculations. Each subclass overrides calculateBill according to its unique billing requirements (e.g., PhoneSubscriber calculates based on call duration, while ISPSubscriber uses data usage with free data allowances). This enables additional subscriber types to be created without altering the base class or existing subclasses.

  3. Flexible to New Requirements: By following OCP, any new type of subscriber can be added—such as PremiumSubscriber or CorporateSubscriber—with its own billing logic without affecting the existing structure. This ensures that the core system remains stable, extensible, and easily maintainable as new features or subscriber types are introduced.

---
title: Open Closed Principle - Class Diagram
---
classDiagram
    class Subscriber {
        +Long subscriberId
        +String address
        +Long phoneNumber
        +int baseRate
        +double calculateBill()
    }
    
    class PhoneSubscriber {
        +double calculateBill()
    }
    
    class ISPSubscriber {
        +double calculateBill()
        +long freeUsage
        +void setFreeUsage(long freeUsage)
        +long getFreeUsage()
    }

    Subscriber <|-- PhoneSubscriber
    Subscriber <|-- ISPSubscriber

1.3 Liskov Substitution Principle

Objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program.

SHOW CODE: Violation of LSP
package dev.signalyu.solid.lsp;

class Rectangle {

    private int width;

    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int computeArea() {
        return width * height;
    }
}

class Square extends Rectangle {

    public Square(int side) {
        super(side, side);
    }

    @Override
    public void setWidth(int width) {
        setSide(width);
    }

    @Override
    public void setHeight(int height) {
        setSide(height);
    }

    public void setSide(int side) {
        super.setWidth(side);
        super.setHeight(side);
    }
}


public class Main {

    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle(10, 20);
        System.out.println(rectangle.computeArea()); // 200 = 10 * 20

        Square square = new Square(10);
        System.out.println(square.computeArea()); // 100 = 10 * 10

        useRectangle(rectangle);
        System.out.println(rectangle.computeArea()); // 600 = 20 * 30

        useRectangle(square); // Violates Liskov Substitution Principle
        System.out.println(square.computeArea()); // 900 = 30 * 30
    }

    private static void useRectangle(Rectangle rectangle) {
        rectangle.setHeight(20);
        rectangle.setWidth(30);
        assert rectangle.getHeight() == 20 : "Height Not equal to 20";
        assert rectangle.getWidth() == 30 : "Width Not equal to 30";
    }
}

In the above code, the useRectangle method assumes that it can independently set the width and height of a Rectangle. However, when a Square (a subclass of Rectangle) object is passed to it, unexpected behavior will occur.


SHOW CODE: After Refactoring
package dev.signalyu.solid.lsp;

interface Shape {
    int computeArea();
}

class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public int computeArea() {
        return width * height;
    }
}

class Square implements Shape {
    private int side;

    public Square(int side) {
        this.side = side;
    }

    public void setSide(int side) {
        this.side = side;
    }

    public int getSide() {
        return side;
    }

    @Override
    public int computeArea() {
        return side * side;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle(10, 20);
        System.out.println(rectangle.computeArea());

        Square square = new Square(10);
        System.out.println(square.computeArea());

        useRectangle(rectangle);
    }

    private static void useRectangle(Rectangle rectangle) {
        rectangle.setHeight(20);
        rectangle.setWidth(30);
        assert rectangle.getHeight() == 20 : "Height Not equal to 20";
        assert rectangle.getWidth() == 30 : "Width Not equal to 30";
    }
}

After refactoring, the useRectangle method is now dedicated exclusively to the Rectangle class.


1.4 Interface Segregation Principle

No client should be forced to depend on methods it does not use.

SHOW CODE: Violation of ISP
package dev.signalyu.solid.isp;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

// Abstract Entity Class
abstract class Entity {
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

// Order Entity
class Order extends Entity {
    private LocalDateTime orderPlacedOn;
    private double totalValue;

    public LocalDateTime getOrderPlacedOn() {
        return orderPlacedOn;
    }

    public void setOrderPlacedOn(LocalDateTime orderPlacedOn) {
        this.orderPlacedOn = orderPlacedOn;
    }

    public double getTotalValue() {
        return totalValue;
    }

    public void setTotalValue(double totalValue) {
        this.totalValue = totalValue;
    }
}

// User Entity
class User extends Entity {
    private String name;
    private LocalDateTime lastLogin;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDateTime getLastLogin() {
        return lastLogin;
    }

    public void setLastLogin(LocalDateTime lastLogin) {
        this.lastLogin = lastLogin;
    }
}

// Persistence Service Interface
interface PersistenceService<T extends Entity> {
    void save(T entity);

    void delete(T entity);

    T findById(Long id);

    /**
     * ISP VIOLATION!!!
     * 
     * Order entity doesn't have a name property and hence doesn't 
     * need to implement the findByName method
     */
    List<T> findByName(String name);
}

// User Persistence Service
class UserPersistenceService implements PersistenceService<User> {
    private static final Map<Long, User> USERS = new HashMap<>();

    @Override
    public void save(User entity) {
        synchronized (USERS) {
            USERS.put(entity.getId(), entity);
        }
    }

    @Override
    public void delete(User entity) {
        synchronized (USERS) {
            USERS.remove(entity.getId());
        }
    }

    @Override
    public User findById(Long id) {
        synchronized (USERS) {
            return USERS.get(id);
        }
    }

    @Override
    public List findByName(String name) {
        synchronized (USERS) {
            return USERS.values().stream()
                    .filter(u -> u.getName().equalsIgnoreCase(name))
                    .collect(Collectors.toList());
        }
    }
}

The above code violates the Interface Segregation Principle because the PersistenceService interface defines methods that are not applicable to all entities. For instance, the Order entity does not have a name property and therefore does not require the findByName method.


SHOW CODE: After Refactoring
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

// Abstract Entity Class
abstract class Entity {
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

// User Entity
class User extends Entity {
    private String name;
    private LocalDateTime lastLogin;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDateTime getLastLogin() {
        return lastLogin;
    }

    public void setLastLogin(LocalDateTime lastLogin) {
        this.lastLogin = lastLogin;
    }
}

// Order Entity
class Order extends Entity {
    private LocalDateTime orderPlacedOn;
    private double totalValue;

    public LocalDateTime getOrderPlacedOn() {
        return orderPlacedOn;
    }

    public void setOrderPlacedOn(LocalDateTime orderPlacedOn) {
        this.orderPlacedOn = orderPlacedOn;
    }

    public double getTotalValue() {
        return totalValue;
    }

    public void setTotalValue(double totalValue) {
        this.totalValue = totalValue;
    }
}

// Persistence Service Interface
interface PersistenceService<T extends Entity> {
    void save(T entity);

    void delete(T entity);

    T findById(Long id);
}

// Order Persistence Service
class OrderPersistenceService implements PersistenceService<Order> {
    private static final Map<Long, Order> ORDERS = new HashMap<>();

    @Override
    public void save(Order entity) {
        synchronized (ORDERS) {
            ORDERS.put(entity.getId(), entity);
        }
    }

    @Override
    public void delete(Order entity) {
        synchronized (ORDERS) {
            ORDERS.remove(entity.getId());
        }
    }

    @Override
    public Order findById(Long id) {
        synchronized (ORDERS) {
            return ORDERS.get(id);
        }
    }
}

// User Persistence Service
class UserPersistenceService implements PersistenceService<User> {
    private static final Map<Long, User> USERS = new HashMap<>();

    @Override
    public void save(User entity) {
        synchronized (USERS) {
            USERS.put(entity.getId(), entity);
        }
    }

    @Override
    public void delete(User entity) {
        synchronized (USERS) {
            USERS.remove(entity.getId());
        }
    }

    @Override
    public User findById(Long id) {
        synchronized (USERS) {
            return USERS.get(id);
        }
    }

    public List findByName(String name) {
        synchronized (USERS) {
            return USERS.values().stream()
                    .filter(u -> u.getName().equalsIgnoreCase(name))
                    .collect(Collectors.toList());
        }
    }
}

After refactoring, the PersistenceService interface includes only the generic methods (save, findById, and delete) that are applicable to all entities.


1.5 Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions

SHOW CODE: Violation of DIP
package dev.signalyu.solid.dip;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.ZoneId;

// Common interface for classes formatting Message object
interface Formatter {
    String format(Message message) throws FormatException;
}

// Thrown by formatter
class FormatException extends IOException {
    public FormatException(Exception cause) {
        super(cause);
    }
}

// Formats message to JSON format
class JSONFormatter implements Formatter {
    public String format(Message message) throws FormatException {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(message);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new FormatException(e);
        }
    }
}

// Message class with content and timestamp
class Message {
    private String msg;
    private LocalDateTime timestamp;

    public Message(String msg) {
        this.msg = msg;
        this.timestamp = LocalDateTime.now(ZoneId.of("UTC"));
    }

    public String getMsg() {
        return msg;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

// Writes message to a file
class MessagePrinter {
    /**
    * MessagePrinter directly instantiates the JSONFormatter object inside the 
    * writeMessage method, which violates the Dependency Inversion Principle.
    */
    public void writeMessage(Message msg, String fileName) throws IOException {
        Formatter formatter = new JSONFormatter(); // creates formatter
        try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { // creates print writer
            writer.println(formatter.format(msg)); // formats and writes message
            writer.flush();
        }
    }
}

// Main class
public class Main {
    public static void main(String[] args) throws IOException {
        Message msg = new Message("This is a message again");
        MessagePrinter printer = new MessagePrinter();
        printer.writeMessage(msg, "test_msg.txt");
    }
}

In the above code, the MessagePrinter class directly instanitiates the JSONFormatter object inside the writeMessage method, which violates the Dependency Inversion Principle. This tight coupling means that if a new formatter, such as TextFormatter, needs to be added, the writeMessage method must be modified. This violates the Open-Closed Principle, which states that classes should be open for extension but closed for modification. According to the Dependency Inversion Principle, the high-level module (MessagePrinter) should not depend on the low-level module (JSONFormatter). Instead, both should depend on abstractions, such as the Formatter interface.


SHOW CODE: After Refactoring
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.ZoneId;

// Common interface for classes formatting Message objects
interface Formatter {
    String format(Message message) throws FormatException;
}

// Custom exception thrown by Formatter
class FormatException extends IOException {
    public FormatException(Exception cause) {
        super(cause);
    }
}

// Formats a Message object to JSON format
class JSONFormatter implements Formatter {
    public String format(Message message) throws FormatException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        try {
            return mapper.writeValueAsString(message);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new FormatException(e);
        }
    }
}

// Represents a message with content and a timestamp
class Message {
    private String msg;
    private LocalDateTime timestamp;

    public Message(String msg) {
        this.msg = msg;
        this.timestamp = LocalDateTime.now(ZoneId.of("UTC"));
    }

    public String getMsg() {
        return msg;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

// Responsible for writing a formatted Message to a file or output stream
class MessagePrinter {
    public void writeMessage(Message msg, Formatter formatter, PrintWriter writer) throws IOException {
        writer.println(formatter.format(msg)); // Formats and writes the message
        writer.flush();
    }
}

// Main class demonstrating functionality
public class Main {
    public static void main(String[] args) throws IOException {
        Message msg = new Message("This is a message again");
        MessagePrinter printer = new MessagePrinter();
        try (PrintWriter writer = new PrintWriter(System.out)) { // Writing to console output
            printer.writeMessage(msg, new JSONFormatter(), writer);
        }
    }
}

After refactoring, the MessagePrinter class now depends on the Formatter abstraction rather than its concrete implementations. This makes it easier to replace JSONFormatter with any other implementation of Formatter without modifying the existing code, adhering to the Open-Closed Principle.


2. Creational Design Patterns

Creational Design Patterns focus on the process of object creation, ensuring flexibility and promoting code reuse.


2.1 Builder

The Builder Design Pattern is a creational design pattern that enables the step-by-step construction of complex objects. It separates the construction process form the object’s representation, allowing the same process to produce different representations. This pattern is often used to build complex or immutable obejcts.


❔❔❔ Same Construction Process Creates Different Representations:

  • Consider building a meal in a fast-food restaurant, where the construction steps include:

    1. Add a main dish (e.g., burger, sandwich, or pizza).
    2. Add a side dish (e.g., fries, salad, or breadsticks).
    3. Add a drink (e.g., soda, coffee, or juice).
  • The construction process remains consistent: (main dish + side dish + drink). However, the final representation varies based on the choices:

    1. Veggie Meal: Sandwich + Salad + Juice.
    2. Classic Meal: Burger + Fries + Soda.
    3. Kids’ Meal: Mini Pizza + Fries + Milk.

---
title: UML Diagram of Builder Pattern
---
classDiagram
    class Builder {
        +buildPartA()
        +buildPartB()
        +getResult()
    }
    
    class ConcreteBuilder1 {
        +buildPartA()
        +buildPartB()
        +getResult()
    }

    class ConcreteBuilder2 {
        +buildPartA()
        +buildPartB()
        +getResult()
    }

    class Director {
        +construct(builder: Builder)
    }

    class Product {
        +PartA
        +PartB
    }

    Builder <|-- ConcreteBuilder1
    Builder <|-- ConcreteBuilder2
    Director --> Builder : uses
    ConcreteBuilder1 --> Product : creates
    ConcreteBuilder2 --> Product : creates

ℹ️ In a context of design patterns, UML (Unified Modeling Language) is commonly used to represent the structure and behavior of the patterns in a clear and simple manner.


SHOW CODE: Director
import java.time.LocalDate;
import java.time.Period;
import java.util.Objects;

// Address class
class Address {
    private String houseNumber;
    private String street;
    private String city;
    private String zipcode;
    private String state;

    public Address(String houseNumber, String street, String city, String state, String zipcode) {
        this.houseNumber = houseNumber;
        this.street = street;
        this.city = city;
        this.state = state;
        this.zipcode = zipcode;
    }

    public String getFullAddress() {
        return houseNumber + ", " + street + "\n" + city + "\n" + state + " " + zipcode;
    }
}

// User class
class User {
    private String firstName;
    private String lastName;
    private LocalDate birthday;
    private Address address;

    public User(String firstName, String lastName, LocalDate birthday, Address address) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthday = birthday;
        this.address = address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public Address getAddress() {
        return address;
    }
}

// UserDTO interface
interface UserDTO {
    String getName();

    String getAddress();

    String getAge();
}

// UserWebDTO implementation
class UserWebDTO implements UserDTO {
    private final String name;
    private final String address;
    private final String age;

    public UserWebDTO(String name, String address, String age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getAddress() {
        return address;
    }

    @Override
    public String getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Name: " + name + "\nAge: " + age + "\nAddress:\n" + address;
    }
}

// UserDTOBuilder interface
interface UserDTOBuilder {
    UserDTOBuilder withFirstName(String firstName);

    UserDTOBuilder withLastName(String lastName);

    UserDTOBuilder withBirthday(LocalDate birthday);

    UserDTOBuilder withAddress(Address address);

    UserDTO build();
}

// Concrete builder
class UserWebDTOBuilder implements UserDTOBuilder {
    private String firstName;
    private String lastName;
    private String age;
    private String address;

    @Override
    public UserDTOBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    @Override
    public UserDTOBuilder withLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

    @Override
    public UserDTOBuilder withBirthday(LocalDate birthday) {
        if (Objects.nonNull(birthday)) {
            this.age = String.valueOf(Period.between(birthday, LocalDate.now()).getYears());
        }
        return this;
    }

    @Override
    public UserDTOBuilder withAddress(Address address) {
        if (Objects.nonNull(address)) {
            this.address = address.getFullAddress();
        }
        return this;
    }

    @Override
    public UserDTO build() {
        return new UserWebDTO(firstName + " " + lastName, address, age);
    }
}

// Client code (Director)
public class Client {
    public static void main(String[] args) {
        User user = createUser();
        UserDTOBuilder builder = new UserWebDTOBuilder();
        UserDTO userDTO = buildUserDTO(builder, user);
        System.out.println(userDTO);
    }

    private static UserDTO buildUserDTO(UserDTOBuilder builder, User user) {
        return builder.withFirstName(user.getFirstName())
                .withLastName(user.getLastName())
                .withBirthday(user.getBirthday())
                .withAddress(user.getAddress())
                .build();
    }

    private static User createUser() {
        Address address = new Address("100", "State Street", "Pawnee", "Indiana", "47998");
        return new User("Ron", "Swanson", LocalDate.of(1960, 5, 6), address);
    }
}
SHOW CODE: Inner Builder
import java.time.LocalDate;
import java.time.Period;

// Address class with Builder
class Address {
    private final String houseNumber;
    private final String street;
    private final String city;
    private final String zipcode;
    private final String state;

    private Address(Builder builder) {
        this.houseNumber = builder.houseNumber;
        this.street = builder.street;
        this.city = builder.city;
        this.zipcode = builder.zipcode;
        this.state = builder.state;
    }

    public String getFullAddress() {
        return houseNumber + ", " + street + "\n" + city + "\n" + state + " " + zipcode;
    }

    public static class Builder {
        private String houseNumber;
        private String street;
        private String city;
        private String zipcode;
        private String state;

        public Builder setHouseNumber(String houseNumber) {
            this.houseNumber = houseNumber;
            return this;
        }

        public Builder setStreet(String street) {
            this.street = street;
            return this;
        }

        public Builder setCity(String city) {
            this.city = city;
            return this;
        }

        public Builder setZipcode(String zipcode) {
            this.zipcode = zipcode;
            return this;
        }

        public Builder setState(String state) {
            this.state = state;
            return this;
        }

        public Address build() {
            return new Address(this);
        }
    }
}

// User class with Builder
class User {
    private final String firstName;
    private final String lastName;
    private final LocalDate birthday;
    private final Address address;

    private User(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.birthday = builder.birthday;
        this.address = builder.address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public Address getAddress() {
        return address;
    }

    public static class Builder {
        private String firstName;
        private String lastName;
        private LocalDate birthday;
        private Address address;

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder setBirthday(LocalDate birthday) {
            this.birthday = birthday;
            return this;
        }

        public Builder setAddress(Address address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// UserDTO interface
interface UserDTO {
    String getName();

    String getAddress();

    String getAge();
}

// UserWebDTO implementation
class UserWebDTO implements UserDTO {
    private final String name;
    private final String address;
    private final String age;

    private UserWebDTO(Builder builder) {
        this.name = builder.name;
        this.address = builder.address;
        this.age = builder.age;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getAddress() {
        return address;
    }

    @Override
    public String getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Name: " + name + "\nAge: " + age + "\nAddress:\n" + address;
    }

    public static class Builder {
        private String name;
        private String address;
        private String age;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }

        public Builder setAge(String age) {
            this.age = age;
            return this;
        }

        public UserWebDTO build() {
            return new UserWebDTO(this);
        }
    }
}

// Client code
public class Client {
    public static void main(String[] args) {
        Address address = new Address.Builder()
                .setHouseNumber("100")
                .setStreet("State Street")
                .setCity("Pawnee")
                .setState("Indiana")
                .setZipcode("47998")
                .build();

        User user = new User.Builder()
                .setFirstName("Ron")
                .setLastName("Swanson")
                .setBirthday(LocalDate.of(1960, 5, 6))
                .setAddress(address)
                .build();

        UserWebDTO userDTO = new UserWebDTO.Builder()
                .setName(user.getFirstName() + " " + user.getLastName())
                .setAddress(user.getAddress().getFullAddress())
                .setAge(String.valueOf(Period.between(user.getBirthday(), LocalDate.now()).getYears()))
                .build();

        System.out.println(userDTO);
    }
}

Summary

Why builder pattern is capable of creating immutable objects?

Implementing the builder as a static inner class facilitates the creation of immutable objects. The builder class has provileged access to the enclosing class’s internal state during the construction process. However, once the final object is built, it does not expose any setters or methods that modify its state, ensuring immutability.


The Director in the Builder Design Pattern is responsible for the construction process but is often not implemented as a standalone class. Instead, its responsibilities are typically handled by the client code or the consumer that requires the constructed object. This approach avoids the need for a separate director class, especially when the construction logic is simple or tailored to the client’s specific requirements.

SHOW CODE: Without a Director
UserDTO userDTO = new UserWebDTOBuilder()
                    .withFirstName("John")
                    .withLastName("Doe")
                    .withBirthday(LocalDate.of(1990, 1, 1))
                    .withAddress(new Address("123", "Main St", "City", "State", "12345"))
                    .build();
SHOW CODE: With a Director
public class Director {
    public UserDTO constructUser(UserDTOBuilder builder) {
        return builder.withFirstName("John")
                      .withLastName("Doe")
                      .withBirthday(LocalDate.of(1990, 1, 1))
                      .withAddress(new Address("123", "Main St", "City", "State", "12345"))
                      .build();
    }
}

If a Product in the Builder Design Pattern is not part of an inheritance hierarchy, creating an abstract builder is unnecessary. The abstract builder is typically used to define method for mutiple types of builders. When there is only one type of product to construct, a concrete builder alone is sufficient to meet the requirements.


2.2 Simple Factory

The Simple Factory design pattern is a creational design pattern that delegates the responsibility of creating instances of different classes to a factory class, typically using a static method based on input parameters. This pattern centralizes object creation, simplifying the client code. However, it can violate the Open-Closed Principle (OCP), because adding a new product type requires modifying the factory class itself. This makes the code less flexible and harder to extend without altering existing source code.


SHOW CODE
import java.time.LocalDateTime;
import java.time.LocalDate;

// Abstract Post class
public abstract class Post {
    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdOn;
    private LocalDateTime publishedOn;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public LocalDateTime getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(LocalDateTime createdOn) {
        this.createdOn = createdOn;
    }

    public LocalDateTime getPublishedOn() {
        return publishedOn;
    }

    public void setPublishedOn(LocalDateTime publishedOn) {
        this.publishedOn = publishedOn;
    }
}

// BlogPost class extends Post
public class BlogPost extends Post {
    private String author;
    private String[] tags;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String[] getTags() {
        return tags;
    }

    public void setTags(String[] tags) {
        this.tags = tags;
    }
}

// NewsPost class extends Post
public class NewsPost extends Post {
    private String headline;
    private LocalDate newsTime;

    public String getHeadline() {
        return headline;
    }

    public void setHeadline(String headline) {
        this.headline = headline;
    }

    public LocalDate getNewsTime() {
        return newsTime;
    }

    public void setNewsTime(LocalDate newsTime) {
        this.newsTime = newsTime;
    }
}

// ProductPost class extends Post
public class ProductPost extends Post {
    private String imageUrl;
    private String name;

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

// PostFactory class to create different types of posts
public class PostFactory {
    public static Post createPost(String type) {
        switch (type) {
            case "blog":
                return new BlogPost();
            case "news":
                return new NewsPost();
            case "product":
                return new ProductPost();
            default:
                throw new IllegalArgumentException("Post type is unknown");
        }
    }
}

// Client class to demonstrate the usage of Simple Factory
public class Client {
    public static void main(String[] args) {
        Post post = PostFactory.createPost("news");
        System.out.println(post.getClass().getSimpleName()); // Prints the type of the created post (NewsPost)
    }
}
classDiagram
    direction RL
    class Post {
        - Long id
        - String title
        - String content
        - LocalDateTime createdOn
        - LocalDateTime publishedOn
        + getId()
        + setId(Long)
        + getTitle()
        + setTitle(String)
        + getContent()
        + setContent(String)
        + getCreatedOn()
        + setCreatedOn(LocalDateTime)
        + getPublishedOn()
        + setPublishedOn(LocalDateTime)
    }

    class BlogPost {
        - String author
        - String[] tags
        + getAuthor()
        + setAuthor(String)
        + getTags()
        + setTags(String[])
    }

    class NewsPost {
        - String headline
        - LocalDate newsTime
        + getHeadline()
        + setHeadline(String)
        + getNewsTime()
        + setNewsTime(LocalDate)
    }

    class ProductPost {
        - String imageUrl
        - String name
        + getImageUrl()
        + setImageUrl(String)
        + getName()
        + setName(String)
    }

    class PostFactory {
        + createPost(String)
    }

    Post <|-- BlogPost
    Post <|-- NewsPost
    Post <|-- ProductPost

    PostFactory --|> BlogPost : creates
    PostFactory --|> NewsPost : creates
    PostFactory --|> ProductPost : creates

    

The above code violates the Open-Closed Principle because when adding a new post type, such as EventPost, to the factory, the code must be modified by adding a new case statement, like the following:

case "event":
    return new EventPost();

This approach breaks the OCP, which dictates that entities should be open for extension but closed for modification.


2.3 Factory Method

The Factory Method design pattern is a creational pattern that provides a method for creating objects in a superclass, while allowing subclasses to determine the type of object that will be created. This approach delegates the object creation process to the subclasses, enabling them to instantiate specific classes without the superclass needing to know the exact class being instantiated.


SHOW CODE
import java.util.*;

/**
 * Abstract class representing a message, which is the "product".
 * Subclasses will provide specific content types.
 */
abstract class Message {

    public abstract String getContent();
    
    public void addDefaultHeaders() {
        // Adds some default headers
    }
    
    public void encrypt() {
        // Has some code to encrypt the content
    }
}

/**
 * Concrete implementation of the Message class for Text messages.
 */
class TextMessage extends Message {

    @Override
    public String getContent() {
        return "Text";
    }
}

/**
 * Concrete implementation of the Message class for JSON messages.
 */
class JSONMessage extends Message {

    @Override
    public String getContent() {
        return "{\"JSON\":[]}";
    }
}

/**
 * Abstract creator class. The factory method createMessage() must be implemented
 * by subclasses to instantiate the specific message type.
 */
abstract class MessageCreator {

    public Message getMessage() {
        Message msg = createMessage();
        
        msg.addDefaultHeaders();
        msg.encrypt();
        
        return msg;
    }
    
    // Factory method to create a message
    protected abstract Message createMessage();
}

/**
 * Concrete creator for creating JSON messages.
 */
class JSONMessageCreator extends MessageCreator {

    @Override
    public Message createMessage() {
        return new JSONMessage();
    }
}

/**
 * Concrete creator for creating Text messages.
 */
class TextMessageCreator extends MessageCreator {

    @Override
    public Message createMessage() {
        return new TextMessage();
    }
}

/**
 * Client class demonstrating the use of the Factory Method pattern.
 */
public class Client {

    public static void main(String[] args) {
        printMessage(new JSONMessageCreator());
        printMessage(new TextMessageCreator());
    }
    
    public static void printMessage(MessageCreator creator) {
        Message msg = creator.getMessage();
        System.out.println(msg.getContent());
    }
}
---
title: UML Diagram of Factory Method Pattern
---
classDiagram
    Message <|-- TextMessage
    Message <|-- JSONMessage
    MessageCreator <|-- JSONMessageCreator
    MessageCreator <|-- TextMessageCreator

    JSONMessageCreator "1" --> "1" JSONMessage : creates
    TextMessageCreator "1" --> "1" TextMessage : creates

    MessageCreator : +getMessage()
    MessageCreator : +createMessage()

    JSONMessageCreator : +createMessage() 
    TextMessageCreator : +createMessage()

    Message : +getContent() 
    Message : +addDefaultHeaders()
    Message : +encrypt()

2.4 Prototype

The Prototype design pattern is a creational pattern that enables the creation of new objects by copying an existing object, known as the prototype. It is particularly useful when object creation is resource-intensive or when there is a need to dynamically create mana similar objects.

SHOW CODE
// Point3D class representing a 3D point
class Point3D {
    private float x, y, z;

    public static final Point3D ZERO = new Point3D(0, 0, 0);

    public Point3D(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point3D normalize() {
        float mag = magnitude();
        return new Point3D(x / mag, y / mag, z / mag);
    }

    private float magnitude() {
        return (float) Math.sqrt(x * x + y * y + z * z);
    }

    public Point3D multiply(float scale) {
        return new Point3D(x * scale, y * scale, z * scale);
    }

    public Point3D add(Point3D vector) {
        return new Point3D(x + vector.x, y + vector.y, z + vector.z);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ", " + z + ")";
    }
}

// Abstract GameUnit class defining the clone method
abstract class GameUnit implements Cloneable {
    private Point3D position;

    public GameUnit() {
        position = Point3D.ZERO;
    }

    @Override
    public GameUnit clone() throws CloneNotSupportedException {
        GameUnit unit = (GameUnit) super.clone();
        unit.initialize();
        return unit;
    }

    protected void initialize() {
        this.position = Point3D.ZERO;
        reset();
    }

    protected abstract void reset();

    public GameUnit(float x, float y, float z) {
        position = new Point3D(x, y, z);
    }

    public void move(Point3D direction, float distance) {
        Point3D finalMove = direction.normalize();
        finalMove = finalMove.multiply(distance);
        position = position.add(finalMove);
    }

    public Point3D getPosition() {
        return position;
    }
}

// Swordsman class extends GameUnit and implements specific behavior
class Swordsman extends GameUnit {
    private String state = "idle";

    public void attack() {
        this.state = "attacking";
    }

    @Override
    public String toString() {
        return "Swordsman " + state + " @ " + getPosition();
    }

    @Override
    protected void reset() {
        state = "idle";
    }
}

// General class extends GameUnit but does not support cloning
class General extends GameUnit {
    private String state = "idle";

    public void boostMorale() {
        this.state = "MoralBoost";
    }

    @Override
    public String toString() {
        return "General " + state + " @ " + getPosition();
    }

    @Override
    public GameUnit clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Generals are unique");
    }

    @Override
    protected void reset() {
        throw new UnsupportedOperationException("Reset not supported");
    }
}

public class Main {
    // Client code to demonstrate the Prototype Pattern
    public static void main(String[] args) throws CloneNotSupportedException {
        // Creating and moving a Swordsman
        Swordsman s1 = new Swordsman();
        s1.move(new Point3D(-10, 0, 0), 20);
        s1.attack();
        System.out.println(s1);

        // Cloning the Swordsman
        Swordsman s2 = (Swordsman) s1.clone();
        System.out.println("Cloned swordsman: " + s2);
    }
}

Shallow Copy vs. Deep Copy: A shallow copy creates a new object but only copies the references of the original object’s fields. As a result, both the original and the copied object share references to the same inner objects. Any changes made to the inner mutable objects of the copied object will affect the original object as well. In Java, the clone method performs a shallow copy. On the other hand, a deep copy involves recursively copying the original object and all the objects it references. As a result, the copied object is entirely independent of the original, and changes made to the copied object do not affect the original one.

SHOW CODE: Shallow Copy
import java.util.ArrayList;
import java.util.List;

/**
 * @author Signal Yu
 * @since 2024-12-2
 */

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

public class Solution {
    public static void main(String[] args) {
        // Original list with mutable objects (Person)
        List<Person> originalList = new ArrayList<>();
        originalList.add(new Person("Alice", 30));
        originalList.add(new Person("Bob", 25));

        // Shallow copy of the list
        List<Person> shallowCopyList = new ArrayList<>(originalList);

        // Modify the object in the shallow copy
        shallowCopyList.get(1).setName("Signal");

        // Print both lists
        System.out.println("Original List: " + originalList);
        System.out.println("Shallow Copy List: " + shallowCopyList);
    }
}
SHOW OUTPUT: Shallow Copy
Original List: [Person{name='Alice', age=30}, Person{name='Signal', age=25}]
Shallow Copy List: [Person{name='Alice', age=30}, Person{name='Signal', age=25}]
SHOW CODE: Deep Copy
import java.util.ArrayList;
import java.util.List;

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }

    // Deep copy method for Person class
    public Person deepCopy() {
        return new Person(this.name, this.age);
    }
}

public class Solution {
    public static void main(String[] args) {
        // Original list with mutable objects (Person)
        List<Person> originalList = new ArrayList<>();
        originalList.add(new Person("Alice", 30));
        originalList.add(new Person("Bob", 25));

        // Deep copy of the list (creating new Person objects)
        List<Person> deepCopyList = new ArrayList<>();
        for (Person person : originalList) {
            deepCopyList.add(person.deepCopy()); // Add a deep copy of each person
        }

        // Modify the object in the deep copy
        deepCopyList.get(1).setName("Signal");

        // Print both lists
        System.out.println("Original List: " + originalList);
        System.out.println("Deep Copy List: " + deepCopyList);
    }
}
SHOW OUTPUT: Deep Copy
Original List: [Person{name='Alice', age=30}, Person{name='Bob', age=25}]
Deep Copy List: [Person{name='Alice', age=30}, Person{name='Signal', age=25}]

It is important to note that changes made to immutable objects in a shallow copied object will not affect the original object. For example, when performing a shallow copy of an ArrayList that contains Integer values, changes made to the Integer of the copied object will not affect the original object because Integer is immutable (since it is marked as final and its value cannot be changed).

SHOW CODE
import java.util.ArrayList;
import java.util.List;

/**
 * @author Signal Yu
 * @since 2024-12-2
 */
public class Solution {
    public static void main(String[] args) {
        // Integer is an immutable class
        List<Integer> original = new ArrayList<>();
        original.add(1);
        original.add(2);

        List<Integer> shallowCopy = new ArrayList<>(original);  // Shallow copy
        shallowCopy.set(0, 99);  // Modify the shallow copy

        System.out.println(original); // Output: [1, 2]
        System.out.println(shallowCopy); // Output: [99, 2]

    }
}

Prototype v.s. Builder

2.5 Abstract Factory

The Abstract Factory design pattern is a creational pattern that creates families of related objects without specifying their concrete classes. It allows the client to instantiate objects from a group of related products while ensuring that these objects are compatible with each other. This pattern is particularly useful when a system needs to be independent of how objects are created, composed, and represented. However, a limitation of this pattern is that when a new product type is added, the base factory typically needs to be modified, which can increase the maintainence overhead.

SHOW CODE
// Represents an abstract product
interface Storage {
    String getId();
}

// Represents a concrete product in a family "Amazon Web Services"
class S3Storage implements Storage {
    public S3Storage(int capacityInMib) {
        System.out.println("Allocated " + capacityInMib + " on S3");
    }

    @Override
    public String getId() {
        return "S31";
    }

    @Override
    public String toString() {
        return "S3 Storage";
    }
}

// Represents a concrete product in a family "Google Cloud Platform"
class GoogleCloudStorage implements Storage {
    public GoogleCloudStorage(int capacityInMib) {
        System.out.println("Allocated " + capacityInMib + " on Google Cloud Storage");
    }

    @Override
    public String getId() {
        return "gcpcs1";
    }

    @Override
    public String toString() {
        return "Google cloud storage";
    }
}

// Represents an abstract product
interface Instance {
    enum Capacity {micro, small, large}

    void start();

    void attachStorage(Storage storage);

    void stop();
}

// Represents a concrete product in a family "Amazon Web Services"
class Ec2Instance implements Instance {
    public Ec2Instance(Capacity capacity) {
        System.out.println("Created Ec2Instance");
    }

    @Override
    public void start() {
        System.out.println("Ec2Instance started");
    }

    @Override
    public void attachStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Ec2Instance");
    }

    @Override
    public void stop() {
        System.out.println("Ec2Instance stopped");
    }

    @Override
    public String toString() {
        return "EC2Instance";
    }
}

// Represents a concrete product in a family "Google Cloud Platform"
class GoogleComputeEngineInstance implements Instance {
    public GoogleComputeEngineInstance(Capacity capacity) {
        System.out.println("Created Google Compute Engine instance");
    }

    @Override
    public void start() {
        System.out.println("Compute engine instance started");
    }

    @Override
    public void attachStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Compute engine instance");
    }

    @Override
    public void stop() {
        System.out.println("Compute engine instance stopped");
    }
}

// Abstract factory with methods defined for each object type.
interface ResourceFactory {
    Instance createInstance(Instance.Capacity capacity);

    Storage createStorage(int capMib);
}

// Factory implementation for Amazon Web Services resources
class AwsResourceFactory implements ResourceFactory {

    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new Ec2Instance(capacity);
    }

    @Override
    public Storage createStorage(int capMib) {
        return new S3Storage(capMib);
    }
}

// Factory implementation for Google Cloud Platform resources
class GoogleResourceFactory implements ResourceFactory {

    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new GoogleComputeEngineInstance(capacity);
    }

    @Override
    public Storage createStorage(int capMib) {
        return new GoogleCloudStorage(capMib);
    }
}

// Client class
public class Client {

    private ResourceFactory factory;

    public Client(ResourceFactory factory) {
        this.factory = factory;
    }

    public Instance createServer(Instance.Capacity cap, int storageMib) {
        Instance instance = factory.createInstance(cap);
        Storage storage = factory.createStorage(storageMib);
        instance.attachStorage(storage);
        return instance;
    }

    public static void main(String[] args) {
        Client aws = new Client(new AwsResourceFactory());
        Instance i1 = aws.createServer(Instance.Capacity.micro, 20480);
        i1.start();
        i1.stop();

        System.out.println("***************************************");
        Client gcp = new Client(new GoogleResourceFactory());
        i1 = gcp.createServer(Instance.Capacity.micro, 20480);
        i1.start();
        i1.stop();
    }
}
SHOW OUTPUT
Created Ec2Instance
Allocated 20480 on S3
Attached S3 Storage to Ec2Instance
Ec2Instance started
Ec2Instance stopped
***************************************
Created Google Compute Engine instance
Allocated 20480 on Google Cloud Storage
Attached Google cloud storage to Compute engine instance
Compute engine instance started
Compute engine instance stopped

UML Diagram of Abstract Factory Pattern


Adding a new product type, such as Engine, may require modifications to the base factory. This is because the new Engine product froms a new combination with Storage and Instance, and the base factory is responsible for creating all the products (i.e., Storage, Instance, and Engine). As a result, the base factory needs to include a new method, createEngine. This modification violates the Open/Closed Principle (OCP), which states that classes should be open for extension but closed for modification.

SHOW CODE
// Represents an abstract product for Storage
interface Storage {
    String getId();
}

// Represents a concrete product in a family "Amazon Web Services" for Storage
class S3Storage implements Storage {
    public S3Storage(int capacityInMib) {
        System.out.println("Allocated " + capacityInMib + " on S3");
    }

    @Override
    public String getId() {
        return "S31";
    }

    @Override
    public String toString() {
        return "S3 Storage";
    }
}

// Represents a concrete product in a family "Google Cloud Platform" for Storage
class GoogleCloudStorage implements Storage {
    public GoogleCloudStorage(int capacityInMib) {
        System.out.println("Allocated " + capacityInMib + " on Google Cloud Storage");
    }

    @Override
    public String getId() {
        return "gcpcs1";
    }

    @Override
    public String toString() {
        return "Google Cloud Storage";
    }
}

// Represents an abstract product for Engine
interface Engine {
    String getName();
}

// Represents a concrete product in a family "Amazon Web Services" for Engine
class Ec2Engine implements Engine {
    String name;

    public Ec2Engine(String name) {
        this.name = name;
        System.out.println(name + " engine selected.");
    }

    @Override
    public String getName() {
        return name;
    }
}

// Represents a concrete product in a family "Google Cloud Platform" for Engine
class GoogleComputeEngine implements Engine {
    String name;

    public GoogleComputeEngine(String name) {
        this.name = name;
        System.out.println(name + " engine selected.");
    }

    @Override
    public String getName() {
        return name;
    }
}

// Represents an abstract product for Instance
interface Instance {
    enum Capacity {micro, small, large}

    void start();

    void attachStorage(Storage storage);

    void stop();

    void useEngine(Engine engine);
}

// Represents a concrete product in a family "Amazon Web Services" for Instance
class Ec2Instance implements Instance {
    public Ec2Instance(Instance.Capacity capacity) {
        System.out.println("Created Ec2Instance");
    }

    @Override
    public void start() {
        System.out.println("Ec2Instance started");
    }

    @Override
    public void attachStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Ec2Instance");
    }

    @Override
    public void stop() {
        System.out.println("Ec2Instance stopped");
    }

    @Override
    public void useEngine(Engine engine) {
        System.out.println("Ec2Instance uses " + engine.getName() + " engine.");
    }

    @Override
    public String toString() {
        return "EC2Instance";
    }
}

// Represents a concrete product in a family "Google Cloud Platform" for Instance
class GoogleComputeEngineInstance implements Instance {

    public GoogleComputeEngineInstance(Instance.Capacity capacity) {
        System.out.println("Created Google Compute Engine instance");
    }

    @Override
    public void start() {
        System.out.println("Compute engine instance started");
    }

    @Override
    public void attachStorage(Storage storage) {
        System.out.println("Attached " + storage + " to Compute engine instance");
    }

    @Override
    public void stop() {
        System.out.println("Compute engine instance stopped");
    }

    @Override
    public void useEngine(Engine engine) {
        System.out.println("Google compute engine instance uses " + engine.getName() + " engine.");
    }
}

// Abstract factory with methods defined for each object type
interface ResourceFactory {
    Instance createInstance(Instance.Capacity capacity);

    Storage createStorage(int capMib);

    Engine createEngine(String name);
}

// Factory implementation for Amazon Web Services resources
class AwsResourceFactory implements ResourceFactory {

    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new Ec2Instance(capacity); // Pass engine to the Ec2Instance
    }

    @Override
    public Storage createStorage(int capMib) {
        return new S3Storage(capMib);
    }

    @Override
    public Engine createEngine(String name) {
        return new Ec2Engine(name);
    }
}

// Factory implementation for Google Cloud Platform resources
class GoogleResourceFactory implements ResourceFactory {
    @Override
    public Instance createInstance(Instance.Capacity capacity) {
        return new GoogleComputeEngineInstance(capacity);
    }

    @Override
    public Storage createStorage(int capMib) {
        return new GoogleCloudStorage(capMib);
    }

    @Override
    public Engine createEngine(String name) {
        return new GoogleComputeEngine(name);
    }
}

// Client class to test the code
public class Client {

    private ResourceFactory factory;

    public Client(ResourceFactory factory) {
        this.factory = factory;
    }

    public Instance createServer(Instance.Capacity cap, int storageMib, String name) {
        Instance instance = factory.createInstance(cap); // Pass engine to instance creation
        Storage storage = factory.createStorage(storageMib);
        Engine engine = factory.createEngine(name);
        instance.attachStorage(storage);
        instance.useEngine(engine); // Call useEngine() on instance, which delegates to engine's getName()
        return instance;
    }

    public static void main(String[] args) {
        Client aws = new Client(new AwsResourceFactory());
        Instance i1 = aws.createServer(Instance.Capacity.micro, 20480, "Ec2");
        i1.start();
        i1.stop();

        System.out.println("***************************************");

        Client gcp = new Client(new GoogleResourceFactory());
        i1 = gcp.createServer(Instance.Capacity.micro, 20480, "GCE");
        i1.start();
        i1.stop();
    }
}

2.6 Singleton

Singleton is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance (typically through a static method). To implement a Singleton, the class constructor must be private, which prevents external classes from creating instances of the class directly, and a global static method should be provided to access the instance. There are two common types of Singleton: Eager Singleton and Lazy Singleton.


2.6.1 Eager Singleton

Eager Singleton ensures that an instance is created as soon as the class is loaded, rather than being created when needed. This guarantees that the instance is ready to use but may cause unnecessary overhead if the instance is never actually used.

SHOW CODE: Eager Singleton
package dev.signalyu.singleton;

public class EagerRegistry {

    private EagerRegistry() {
        // Private constructor to prevent instantiation
    }

    // Eagerly initializing the single instance of EagerRegistry
    private static final EagerRegistry INSTANCE = new EagerRegistry();

    // Public method to provide access to the instance
    public static EagerRegistry getInstance() {
        return INSTANCE;
    }

    // Main method to demonstrate usage
    public static void main(String[] args) {
        // Accessing the singleton instance
        EagerRegistry registry = EagerRegistry.getInstance();
        System.out.println("Singleton instance: " + registry);
    }
}
SHOW OUTPUT
Singleton instance: dev.signalyu.singleton.EagerRegistry@7a81197d

2.6.2 Lazy Singleton

Lazy Singleton ensures that an instance of the class is created only when it is actually needed. This approach helps avoid unnecessary overhead but requires additional mechanisms to ensure thread safety in multi-threaded environments. It is commonly implemented using techniques like Double-Checked Locking or Initialization-on-Demand Holder.

Double-Checked Locking is an optimization technique ensures only one instance of a class is created, even in a multi-threaded environment, while minimizing synchronization overhead. This approach involves two checks: The first check, outside the synchronized block, allows the thread to bypass synchronization if the instance has already been created. The second check, inside the synchronized block, ensures that only one thread can create the instance, even if multiple threads attempt to do so concurrently.

SHOW CODE: Double-Checked Locking
package dev.signalyu.singleton;

public class LazyRegistryWithDCL {

    // Private constructor to prevent external instantiation
    private LazyRegistryWithDCL() {}

    // Volatile keyword ensures visibility of changes across threads
    private static volatile LazyRegistryWithDCL INSTANCE;

    // Public method to provide access to the Singleton instance
    public static LazyRegistryWithDCL getInstance() {
        if (INSTANCE == null) {  // First check (outside synchronized block)
            synchronized (LazyRegistryWithDCL.class) {  // Synchronized block to ensure only one thread can create the instance
                if (INSTANCE == null) {  // Second check (inside synchronized block)
                    INSTANCE = new LazyRegistryWithDCL();  // Lazy initialization
                }
            }
        }
        return INSTANCE;
    }

    // Main method to demonstrate the usage of the Singleton
    public static void main(String[] args) {
        // Accessing the Singleton instance
        LazyRegistryWithDCL registry = LazyRegistryWithDCL.getInstance();
        System.out.println("Singleton instance: " + registry);
    }
}
SHOW OUTPUT
Singleton instance: dev.signalyu.singleton.LazyRegistryWithDCL@7a81197d

The Initialization-on-Demand Holder technique leverages the class loading mechnism to ensure that the Singleton instance is created only when it is first accessed. It takes advantages of the fact that class loading in Java inherently thread-safe. This approach relies on static inner classes, which are not loaded until they are referenced, meaning that the instance is created only when it is actually needed.

SHOW CODE: Initialization-on-Demand Holder
package dev.signalyu.singleton;

public class LazyRegistryIODH {

    // Private constructor to prevent external instantiation
    private LazyRegistryIODH() {
        System.out.println("In LazyRegistryIODH singleton");
    }

    // Static inner class that holds the Singleton instance
    private static class RegistryHolder {
        // The Singleton instance is created when the class is loaded
        static final LazyRegistryIODH INSTANCE = new LazyRegistryIODH();
    }

    // Public method to provide access to the Singleton instance
    public static LazyRegistryIODH getInstance() {
        return RegistryHolder.INSTANCE;
    }

    // Main method to demonstrate the usage of the Singleton
    public static void main(String[] args) {
        // Accessing the Singleton instance
        LazyRegistryIODH registry = LazyRegistryIODH.getInstance();
        System.out.println("Singleton instance: " + registry);
    }
}
SHOW OUTPUT
In LazyRegistryIODH singleton
Singleton instance: dev.signalyu.singleton.LazyRegistryIODH@5ca881b5

2.7 Object Pool

Object Pool Design Pattern is a creational design pattern that manages a collection of reusable objects, rather than frequently creating and destroying objects. These objects are stored in a pool, from which clients can borrow objects, use them, and return them when they are done. This pattern is particularly useful in scenerios where a large number of objects are needed temporarily, and the cost of creating these objects is high, such as resource management (e.g., database connection or thread pools).

SHOW CODE
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Supplier;

// Point2D class
class Point2D {
    private float x, y;

    public Point2D(float x, float y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point2D [x=" + x + ", y=" + y + "]";
    }
}

// Poolable interface
interface Poolable {
    // state reset
    void reset();
}

// Image interface extending Poolable
interface Image extends Poolable {
    void draw();

    Point2D getLocation();

    void setLocation(Point2D location);
}

// Bitmap class implementing Image and Poolable
class Bitmap implements Image {
    private Point2D location;
    private String name;

    public Bitmap(String name) {
        this.name = name;
    }

    @Override
    public void draw() {
        System.out.println("Drawing " + name + " @ " + location);
    }

    @Override
    public Point2D getLocation() {
        return location;
    }

    @Override
    public void setLocation(Point2D location) {
        this.location = location;
    }

    @Override
    public void reset() {
        location = null;
        System.out.println("Bitmap is reset");
    }
}

// ObjectPool class for reusable objects
class ObjectPool {
    private BlockingQueue<T> availablePool;

    public ObjectPool(Supplier<T> creator, int count) {
        availablePool = new LinkedBlockingQueue<>();
        for (int i = 0; i < count; i++) {
            availablePool.offer(creator.get());
        }
    }

    public T get() {
        try {
            return availablePool.take();
        } catch (InterruptedException ex) {
            System.err.println("take() was interrupted");
        }
        return null;
    }

    public void release(T obj) {
        obj.reset();
        try {
            availablePool.put(obj);
        } catch (InterruptedException e) {
            System.err.println("put() was interrupted");
        }
    }
}

// Main class to demonstrate Object Pool usage
public class Client {
    public static void main(String[] args) {
        // Create a pool for Bitmap objects with 5 instances
        ObjectPool<Bitmap> bitmapPool = new ObjectPool<>(() -> new Bitmap("Logo.bmp"), 5);

        // Borrow Bitmap objects from the pool
        Bitmap b1 = bitmapPool.get();
        b1.setLocation(new Point2D(10, 10));
        Bitmap b2 = bitmapPool.get();
        b2.setLocation(new Point2D(-10, 0));

        // Use the objects
        b1.draw();
        b2.draw();

        // Release the objects back to the pool
        bitmapPool.release(b1);
        bitmapPool.release(b2);
    }
}
---
title: UML Diagram of Object Pool Design Pattern
---
classDiagram
    ObjectPool --> Bitmap : uses
    Bitmap --> Point2D : uses
    Poolable <|-- Image : implements
    Image <|-- Bitmap : implements
    Client --> ObjectPool : uses

    class ObjectPool {
        +BlockingQueue~T~ availablePool
        +ObjectPool(Supplier~T~ creator, int count)
        +T get()
        +void release(T obj)
    }

    class Bitmap {
        +Point2D location
        +String name
        +Bitmap(String name)
        +void draw()
        +Point2D getLocation()
        +void setLocation(Point2D location)
        +void reset()
    }

    class Point2D {
        +float x
        +float y
        +Point2D(float x, float y)
        +String toString()
    }

    class Poolable {
        +void reset()
    }

    class Image {
        +void draw()
        +Point2D getLocation()
        +void setLocation(Point2D location)
    }

    class Client {
        +void main()
    }

3. Structural Design Patterns

Structural Design Pattern focus on optimizing object composition, helping achieve better organization and efficiency.

3.1 Adapter

The Adapter Design Pattern is a structural design pattern that allows two incompatible interfaces to work together, enabling them to communicate despite differences in their method signatures. This pattern is commonly used in situations where legacy code cannot be modified but needs to integrate with a new system that has a different interface.

SHOW CODE
// Target Interface
interface Customer {
    String getName();

    String getDesignation();

    String getAddress();
}

// Client code which requires Customer interface.
class BusinessCardDesigner {
    public String designCard(Customer customer) {
        String card = "";
        card += customer.getName();
        card += "\n" + customer.getDesignation();
        card += "\n" + customer.getAddress();
        return card;
    }
}

// An existing class used in our system - Adaptee
class Employee {
    private String fullName;
    private String jobTitle;
    private String officeLocation;

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getJobTitle() {
        return jobTitle;
    }

    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
    }

    public String getOfficeLocation() {
        return officeLocation;
    }

    public void setOfficeLocation(String officeLocation) {
        this.officeLocation = officeLocation;
    }
}

// An object adapter using composition to translate the interface
class EmployeeObjectAdapter implements Customer {
    private final Employee adaptee;

    public EmployeeObjectAdapter(Employee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public String getName() {
        return adaptee.getFullName();
    }

    @Override
    public String getDesignation() {
        return adaptee.getJobTitle();
    }

    @Override
    public String getAddress() {
        return adaptee.getOfficeLocation();
    }
}

public class Client {
    public static void main(String[] args) {
        // Using Object Adapter
        Employee employee = new Employee();
        populateEmployeeData(employee);
        EmployeeObjectAdapter objectAdapter = new EmployeeObjectAdapter(employee);

        BusinessCardDesigner designer = new BusinessCardDesigner();
        String card = designer.designCard(objectAdapter);
        System.out.println(card);
    }

    private static void populateEmployeeData(Employee employee) {
        employee.setFullName("Signal Yu");
        employee.setJobTitle("Software Engineer");
        employee.setOfficeLocation("Shenzhen, Guangdong Province");
    }
}
---
title: UML Diagram of Adapter Design Pattern
---
classDiagram
    class Customer {
        +String getName()
        +String getDesignation()
        +String getAddress()
    }

    class BusinessCardDesigner {
        +String designCard(Customer customer)
    }

    class Employee {
        -String fullName
        -String jobTitle
        -String officeLocation
        +String getFullName()
        +String getJobTitle()
        +String getOfficeLocation()
        +setFullName(String fullName)
        +setJobTitle(String jobTitle)
        +setOfficeLocation(String officeLocation)
    }

    class EmployeeObjectAdapter {
        -Employee adaptee
        +String getName()
        +String getDesignation()
        +String getAddress()
    }

    Customer <|-- EmployeeObjectAdapter : implements
    BusinessCardDesigner --> Customer : uses
    EmployeeObjectAdapter --> Employee : adapts

3.2 Bridge

The Bridge Design Pattern is a structural design pattern that separates the abstraction and implementation, enabling them to evolve independently without affecting each other.

In the following example, the Queue data structure is decoupled from the implementation of LinkedList. When there is a need to change the way data is stored, this can be done without modifying the Queue class. Additionally, when a new type of list, such as Doubly Linked List, is introduced, it can be done without affecting the behavior of the Queue class.

SHOW CODE
// This is the implementor interface. 
// It defines the basic operations for a LinkedList.
interface LinkedList {

    void addFirst(T element);

    T removeFirst();

    void addLast(T element);

    T removeLast();

    int getSize();
}

// A concrete implementor that uses nodes to implement a Singly Linked List.
// **NOT thread safe**
class SinglyLinkedList implements LinkedList {

    private int size;
    private Node head;
    private Node last;

    private static class Node {
        private final Object data;
        private Node next;

        private Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }
    }

    @Override
    public void addFirst(T element) {
        if (head == null) {
            last = head = new Node(element, null);
        } else {
            head = new Node(element, head);
        }
        size++;
    }

    @Override
    public T removeFirst() {
        if (head == null) {
            return null;
        }
        @SuppressWarnings("unchecked")
        T temp = (T) head.data;
        if (head.next != null) {
            head = head.next;
        } else {
            head = null;
            last = null;
        }
        size--;
        return temp;
    }

    @Override
    public void addLast(T element) {
        if (last == null) {
            last = head = new Node(element, null);
        } else {
            last.next = new Node(element, null);
            last = last.next;
        }
        size++;
    }

    @Override
    public T removeLast() {
        if (last == null) {
            return null;
        }
        if (head == last) {
            @SuppressWarnings("unchecked")
            T temp = (T) head.data;
            head = last = null;
            size--;
            return temp;
        }
        // since we don't have a back pointer
        Node temp = head;
        while (temp.next != last) {
            temp = temp.next;
        }
        @SuppressWarnings("unchecked")
        T result = (T) last.data;
        last = temp;
        last.next = null;
        size--;
        return result;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder("[");
        Node temp = head;
        while (temp != null) {
            result.append(temp.data).append(temp.next == null ? "" : ", ");
            temp = temp.next;
        }
        result.append("]");
        return result.toString();
    }
}

// A concrete implementor that uses arrays to implement a LinkedList.
// **NOT thread safe**
class ArrayLinkedList implements LinkedList {

    private static final int DEFAULT_SIZE = 2;
    private Object[] data;
    private int size;

    public ArrayLinkedList() {
        data = new Object[DEFAULT_SIZE];
    }

    @Override
    public void addFirst(T element) {
        ensureCapacity(++size);
        shiftOneRight();
        data[0] = element;
    }

    @Override
    public T removeFirst() {
        if (size == 0) {
            return null;
        }
        @SuppressWarnings("unchecked")
        T first = (T) data[0];
        size--;
        shiftOneLeft();
        return first;
    }

    @Override
    public void addLast(T element) {
        ensureCapacity(size + 1);
        data[size++] = element;
    }

    @Override
    @SuppressWarnings("unchecked")
    public T removeLast() {
        if (size == 0) {
            return null;
        }
        return (T) data[size--];
    }

    private void ensureCapacity(int newSize) {
        if (data.length > newSize) {
            return;
        }
        Object[] temp = new Object[data.length + DEFAULT_SIZE];
        System.arraycopy(data, 0, temp, 0, data.length);
        data = temp;
    }

    private void shiftOneRight() {
        System.arraycopy(data, 0, data, 1, size);
    }

    private void shiftOneLeft() {
        System.arraycopy(data, 1, data, 0, size);
    }

    public int getSize() {
        return size;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            result.append(data[i]).append(i == size - 1 ? "" : ", ");
        }
        result.append("]");
        return result.toString();
    }
}

// This is the abstraction interface representing a FIFO collection.
interface FifoCollection {

    // Adds an element to the collection
    void offer(T element);

    // Removes & returns the first element from the collection
    T poll();
}

// A refined abstraction that uses LinkedList to implement a Queue.
class Queue implements FifoCollection {

    private final LinkedList list;

    public Queue(LinkedList list) {
        this.list = list;
    }

    @Override
    public void offer(T element) {
        list.addLast(element);
    }

    @Override
    public T poll() {
        return list.removeFirst();
    }

    @Override
    public String toString() {
        return "Queue{" +
                "list=" + list.toString() +
                '}';
    }
}

// Client code to demonstrate the bridge pattern
public class Client {

    public static void main(String[] args) {
        FifoCollection<Integer> arrayQueue = new Queue<>(new ArrayLinkedList<>());
        arrayQueue.offer(1);
        arrayQueue.offer(2);
        arrayQueue.offer(3);

        System.out.print("Array List Queue: ");
        System.out.println(arrayQueue);
        
        FifoCollection<Integer> linkedListQueue = new Queue<>(new SinglyLinkedList<>());
        linkedListQueue.offer(11);
        linkedListQueue.offer(22);
        linkedListQueue.offer(33);

        System.out.print("Linked List Queue: ");
        System.out.println(linkedListQueue);
        System.out.print(linkedListQueue.poll() + " ");
        System.out.print(linkedListQueue.poll() + " ");
        System.out.print(linkedListQueue.poll() + "\n");

        // Should print null as all elements are removed
        System.out.println(linkedListQueue.poll()); // null
    }
}
SHOW OUTPUT
Array List Queue: Queue{list=[1, 2, 3]}
Linked List Queue: Queue{list=[11, 22, 33]}
11 22 33
null
---
title: UML Diagram of Adapter Design Pattern
---
classDiagram
    class LinkedList {
        +addFirst(T element)
        +removeFirst()
        +addLast(T element)
        +removeLast()
        +getSize()
    }

    class SinglyLinkedList {
        -int size
        -Node head
        -Node last
        +addFirst(T element)
        +removeFirst()
        +addLast(T element)
        +removeLast()
        +getSize()
        +toString()
    }

    class ArrayLinkedList {
        -Object[] data
        -int size
        +addFirst(T element)
        +removeFirst()
        +addLast(T element)
        +removeLast()
        +getSize()
        +toString()
    }

    class FifoCollection {
        +offer(T element)
        +poll()
    }

    class Queue {
        -LinkedList list
        +offer(T element)
        +poll()
        +toString()
    }


    LinkedList <|.. SinglyLinkedList
    LinkedList <|.. ArrayLinkedList
    FifoCollection <|.. Queue : implements
    Queue *--> LinkedList : composites

    %% Client code class
    class Client {
        +main(String[] args)
    }

    Client --> FifoCollection : Uses

3.3 Decorator

Decorator Design Pattern is a structural design pattern that allows addding new behaviors to an object dynamically, without altering its structure.

SHOW CODE
import org.apache.commons.text.StringEscapeUtils;

import java.util.Base64;

// Base interface or component
interface Message {
    String getContent();
}

// Concrete component. Object to be decorated
class TextMessage implements Message {

    private String msg;

    public TextMessage(String msg) {
        this.msg = msg;
    }

    @Override
    public String getContent() {
        return msg;
    }
}

// Decorator. Implements component interface
class HtmlEncodedMessage implements Message {

    private Message msg;

    public HtmlEncodedMessage(Message msg) {
        this.msg = msg;
    }

    @Override
    public String getContent() {
        return StringEscapeUtils.escapeHtml4(msg.getContent());
    }
}

// Decorator for Base64 encoding
class Base64EncodedMessage implements Message {

    private Message msg;

    public Base64EncodedMessage(Message msg) {
        this.msg = msg;
    }

    @Override
    public String getContent() {
        // Be wary of charset!! This is platform dependent..
        return Base64.getEncoder().encodeToString(msg.getContent().getBytes());
    }
}

// Client to demonstrate the decorators
public class Client {

    public static void main(String[] args) {
        Message m = new TextMessage("The  is strong with this one!");
        System.out.println("Original: " + m.getContent());

        // Apply HTML encoding
        Message decorator = new HtmlEncodedMessage(m);
        System.out.println("HTML Encoded: " + decorator.getContent());

        // Apply Base64 encoding
        decorator = new Base64EncodedMessage(decorator);
        System.out.println("Base64 Encoded: " + decorator.getContent());
    }
}
---
title: UML Diagram of Decorator Design Pattern
---
classDiagram
    class Message {
        +String getContent()
    }
    
    class TextMessage {
        -String msg
        +TextMessage(String msg)
        +String getContent()
    }
    
    class HtmlEncodedMessage {
        -Message msg
        +HtmlEncodedMessage(Message msg)
        +String getContent()
    }
    
    class Base64EncodedMessage {
        -Message msg
        +Base64EncodedMessage(Message msg)
        +String getContent()
    }
    
    Message <|-- TextMessage
    Message <|-- HtmlEncodedMessage
    Message <|-- Base64EncodedMessage
    TextMessage --> HtmlEncodedMessage : Wraps
    HtmlEncodedMessage --> Base64EncodedMessage : Wraps

3.4 Composite

The Composite Design Pattern is a structural pattern that enables treating both individual objects and compositions of objects uniformly. It is designed to represent part-whole hierarchies, where individual objects (leaf nodes) and their composites are handled in the same way. This pattern is often used for managing complex hierarchical structures, such as file systems or organization structures.

SHOW CODE
import java.util.ArrayList;
import java.util.List;

// The component base class for composite pattern
// Defines operations applicable both leaf & composite
abstract class File {

    private String name;

    public File(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract void ls();

    public abstract void addFile(File file);

    public abstract File[] getFiles();

    public abstract boolean removeFile(File file);
}

// Leaf node in composite pattern
class BinaryFile extends File {

    private long size;

    public BinaryFile(String name, long size) {
        super(name);
        this.size = size;
    }

    @Override
    public void ls() {
        System.out.println(getName() + "\t" + size);
    }

    @Override
    public void addFile(File file) {
        throw new UnsupportedOperationException("Leaf node doesn't support add operation");
    }

    @Override
    public File[] getFiles() {
        throw new UnsupportedOperationException("Leaf node doesn't support get operation");
    }

    @Override
    public boolean removeFile(File file) {
        throw new UnsupportedOperationException("Leaf node doesn't support remove operation");
    }
}

// Composite in the composite pattern
class Directory extends File {

    private List<File> children = new ArrayList<>();

    public Directory(String name) {
        super(name);
    }

    @Override
    public void ls() {
        System.out.println(getName());
        children.forEach(File::ls);
    }

    @Override
    public void addFile(File file) {
        children.add(file);
    }

    @Override
    public File[] getFiles() {
        return children.toArray(new File[children.size()]);
    }

    @Override
    public boolean removeFile(File file) {
        return children.remove(file);
    }
}

// Client class that creates and displays directory structure
public class Client {

    public static void main(String[] args) {
        File root1 = createTreeOne();
        root1.ls();
        System.out.println("***********************************");
        File root2 = createTreeTwo();
        root2.ls();
    }

    // Client builds tree using leaf and composites
    private static File createTreeOne() {
        File file1 = new BinaryFile("file1", 1000);
        File file2 = new BinaryFile("file2", 2000);
        Directory dir1 = new Directory("dir1");
        dir1.addFile(file1);
        dir1.addFile(file2);

        File file3 = new BinaryFile("file3", 3000);
        Directory dir2 = new Directory("dir2");
        dir2.addFile(file3);
        dir2.addFile(dir1);
        return dir2;
    }

    private static File createTreeTwo() {
        return new BinaryFile("Another file", 200);
    }
}

In the above code, the statements dir2.addFile(file3); and dir2.addFile(dir1); demonstrate how both individual objects (file3) and compositions of objects (file1 and file2) are treated in the same way.

---
title: UML Diagram of Composite Design Pattern
---
classDiagram
    class File {
        +String name
        +getName()
        +setName(String name)
        +ls()
        +addFile(File file)
        +getFiles()
        +removeFile(File file)
    }

    class BinaryFile {
        +long size
        +BinaryFile(String name, long size)
        +ls()
        +addFile(File file)
        +getFiles()
        +removeFile(File file)
    }

    class Directory {
        +List~File~ children
        +Directory(String name)
        +ls()
        +addFile(File file)
        +getFiles()
        +removeFile(File file)
    }

    File <|-- BinaryFile
    File <|-- Directory
    Directory  *--> File : contains

3.5 Facade

The Facade Design Pattern is a structural design pattern that provides a simplified interface to a complex subsystem. It is often used when the client needs to interact with only a specific part of a large system, hiding its complexity and making it easier to use.

SHOW CODE
package dev.signalyu.facade;

// Template class and related classes
abstract class Template {

    public enum TemplateType {Email, NewsLetter}

    public abstract String format(Object obj);
}

class TemplateFactory {

    public static Template createTemplateFor(Template.TemplateType type) {
        switch (type) {
            case Email:
                return new OrderEmailTemplate();
            default:
                throw new IllegalArgumentException("Unknown TemplateType");
        }
    }
}

class OrderEmailTemplate extends Template {

    @Override
    public String format(Object obj) {
        return "TEMPLATE";
    }
}

// Stationary class and related classes
interface Stationary {

    String getHeader();

    String getFooter();
}

class StationaryFactory {

    public static Stationary createStationary() {
        return new HalloweenStationary();
    }
}

class HalloweenStationary implements Stationary {

    @Override
    public String getHeader() {
        return "It's Halloween!!";
    }

    @Override
    public String getFooter() {
        return "BUY MORE STUFF! It's Halloween, c'mon!!";
    }
}

// Email class and related classes
class EmailBuilder {

    public EmailBuilder withTemplate(Template template) {
        return this;
    }

    public EmailBuilder withStationary(Stationary stationary) {
        return this;
    }

    public EmailBuilder forObject(Object object) {
        return this;
    }

    public Email build() {
        return new Email();
    }

    public Email getEmail() {
        return new Email();
    }
}

class Email {

    public static EmailBuilder getBuilder() {
        return new EmailBuilder();
    }
}

// Mailer class
class Mailer {

    private static final Mailer MAILER = new Mailer();

    public static Mailer getMailer() {
        return MAILER;
    }

    private Mailer() {
    }

    public boolean send(Email email) {
        return true;
    }
}


// Order class for reference (assuming Order exists as a placeholder)
class Order {

    private String id;

    private double total;

    public Order(String id, double total) {
        this.id = id;
        this.total = total;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getTotal() {
        return total;
    }

    public void setTotal(double total) {
        this.total = total;
    }
}

// Facade provides simple methods for the client to use
class EmailFacade {
    public boolean sendOrderEmail(Order order) {
        Template template = TemplateFactory.createTemplateFor(Template.TemplateType.Email);
        Stationary stationary = StationaryFactory.createStationary();
        Email email = Email.getBuilder()
                .withTemplate(template)
                .withStationary(stationary)
                .forObject(order)
                .build();
        Mailer mailer = Mailer.getMailer();
        return mailer.send(email);
    }
}

public class Client {
    public static void main(String[] args) {
        Order order = new Order("101", 99.99);
        EmailFacade facade = new EmailFacade();

        boolean result = facade.sendOrderEmail(order);

        System.out.println("Order Email " + (result ? "sent!" : "NOT sent..."));
    }
}

---
title: UML Diagram of Facade Design Pattern
---
classDiagram
    class EmailFacade {
        +sendOrderEmail(order: Order) bool
    }

    class Template {
        +format(obj: Object) String
    }

    class TemplateFactory {
        +createTemplateFor(type: TemplateType) Template
    }

    class OrderEmailTemplate {
        +format(obj: Object) String
    }

    class Stationary {
        +getHeader() String
        +getFooter() String
    }

    class StationaryFactory {
        +createStationary() Stationary
    }

    class HalloweenStationary {
        +getHeader() String
        +getFooter() String
    }

    class EmailBuilder {
        +withTemplate(template: Template) EmailBuilder
        +withStationary(stationary: Stationary) EmailBuilder
        +forObject(object: Object) EmailBuilder
        +build() Email
    }

    class Email {
        +getBuilder() EmailBuilder
    }

    class Mailer {
        +getMailer() Mailer
        +send(email: Email) bool
    }

    EmailFacade --> TemplateFactory : uses
    EmailFacade --> StationaryFactory : uses
    EmailFacade --> Mailer : uses
    TemplateFactory --> OrderEmailTemplate : creates
    StationaryFactory --> HalloweenStationary : creates
    Email --> EmailBuilder : has
    EmailBuilder --> Template : uses
    EmailBuilder --> Stationary : uses
    Mailer --> Email : uses
    EmailBuilder --> Email : creates
    OrderEmailTemplate --> Template : inherits
    HalloweenStationary --> Stationary : implements

3.6 Flyweight

The Flyweight Design Pattern is a structural design pattern used to optimize memory usuage by sharing common data across multiple objects. In this pattern, an object is divided into two categories: intrinsic state and extrinsic state.

  • Intrinsic state refers to the data that is shared and remains consistent across different instances. It is usually stored within the Flyweight object.
  • Extrinsic state, on the other hand, refers to data that can vary between instances. This state is typically managed and provided by the client or the object using the Flyweight.

SHOW CODE
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

// Interface implemented by Flyweights
interface ErrorMessage {
    // Get error message
    String getText(String code);
}

// A concrete Flyweight. Instance is shared
class SystemErrorMessage implements ErrorMessage {

    // some error message $errorCode
    private String messageTemplate;

    // http://somedomain.com/help?error=
    private String helpUrlBase;

    public SystemErrorMessage(String messageTemplate, String helpUrlBase) {
        this.messageTemplate = messageTemplate;
        this.helpUrlBase = helpUrlBase;
    }

    @Override
    public String getText(String code) {
        return messageTemplate.replace("$errorCode", code) + helpUrlBase + code;
    }
}

// Unshared concrete flyweight.
class UserBannedErrorMessage implements ErrorMessage {
    // All state is defined here
    private String caseId;

    private String remarks;

    private Duration banDuration;

    private String msg;

    public UserBannedErrorMessage(String caseId) {
        // Load case info from DB.
        this.caseId = caseId;
        remarks = "You violated terms of use.";
        banDuration = Duration.ofDays(2);
        msg = "You are BANNED. Sorry. \nMore information:\n";
        msg += caseId + "\n";
        msg += remarks + "\n";
        msg += "Banned For:" + banDuration.toHours() + " Hours";
    }

    // We ignore the extrinsic state argument
    @Override
    public String getText(String code) {
        return msg;
    }

    public String getCaseNo() {
        return caseId;
    }
}

// Flyweight factory. Returns shared flyweight based on key
class ErrorMessageFactory {

    // This serves as key for getting flyweight instance
    public enum ErrorType {
        GenericSystemError, PageNotFoundError, ServerError
    }

    private static final ErrorMessageFactory FACTORY = new ErrorMessageFactory();

    public static ErrorMessageFactory getInstance() {
        return FACTORY;
    }

    private Map<ErrorType, SystemErrorMessage> errorMessages = new HashMap<>();

    private ErrorMessageFactory() {
        errorMessages.put(ErrorType.GenericSystemError,
                new SystemErrorMessage("A genetic error of type $errorCode occurred. Please refer to:\n",
                        "https://google.com/q="));
        errorMessages.put(ErrorType.PageNotFoundError,
                new SystemErrorMessage("Page not found. An error of type $errorCode occurred. Please refer to:\n",
                        "https://google.com/q="));
    }

    public SystemErrorMessage getError(ErrorType type) {
        return errorMessages.get(type);
    }

    public UserBannedErrorMessage getUserBannedMessage(String caseId) {
        return new UserBannedErrorMessage(caseId);
    }
}

// Client
public class Client {

    public static void main(String[] args) {

        // Accessing shared flyweight objects
        SystemErrorMessage msg1 = ErrorMessageFactory.getInstance().getError(ErrorMessageFactory.ErrorType.GenericSystemError);
        System.out.println(msg1.getText("4056"));

        // Accessing unshared flyweight objects
        UserBannedErrorMessage msg2 = ErrorMessageFactory.getInstance().getUserBannedMessage("1202");
        System.out.println(msg2.getText(null));
    }
}
SHOW OUTPUT
A genetic error of type 4056 occurred. Please refer to:
https://google.com/q=4056
You are BANNED. Sorry. 
More information:
1202
You violated terms of use.
Banned For:48 Hours

---
title: UML Diagram of Flyweight Design Pattern
---
classDiagram
    class ErrorMessage {
        +getText(code: String): String
    }

    class SystemErrorMessage {
        -messageTemplate: String
        -helpUrlBase: String
        +getText(code: String): String
    }

    class UserBannedErrorMessage {
        -caseId: String
        -remarks: String
        -banDuration: Duration
        -msg: String
        +getText(code: String): String
        +getCaseNo(): String
    }

    class ErrorMessageFactory {
        -errorMessages: Map~ErrorType, SystemErrorMessage~
        +getInstance(): ErrorMessageFactory
        +getError(type: ErrorType): SystemErrorMessage
        +getUserBannedMessage(caseId: String): UserBannedErrorMessage
    }

    class Client {
        +main(args: String[]): void
    }

    ErrorMessage <|-- SystemErrorMessage
    ErrorMessage <|-- UserBannedErrorMessage
    ErrorMessageFactory *--> SystemErrorMessage
    ErrorMessageFactory *--> UserBannedErrorMessage
    Client --> ErrorMessageFactory

3.7 Proxy

The Proxy Design Pattern is a structural design pattern that involves using a proxy object to represent another object. The proxy controls access to the real object and can add additional functionality, such aas lazy initialization, logging, or monitoring. This pattern can be primarily categorized into two types: Static Proxy and Dynamic Proxy.

The Static Proxy is created at compile time, where a separate proxy class is written to manage method calls to the real object. In contrast, the Dynamic Proxy is created at runtime, often using reflection techniques in languages like Java. A dynamic proxy delegates method calls to the real object without the need for manually creating a proxy class.


SHOW CODE: Static Proxy
// Point2D class to represent the location of the image
class Point2D {

    private final float x;
    private final float y;

    public Point2D(float x, float y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point2D [x=" + x + ", y=" + y + "]";
    }
}

// Interface implemented by proxy and concrete objects
interface Image {
    void setLocation(Point2D point2d);
    Point2D getLocation();
    void render();
}

// Concrete class providing actual functionality
class BitmapImage implements Image {

    private Point2D location;
    private final String name;

    public BitmapImage(String filename) {
        // Loads image from file on disk
        System.out.println("Loaded from disk: " + filename);
        name = filename;
    }

    @Override
    public void setLocation(Point2D point2d) {
        location = point2d;
    }

    @Override
    public Point2D getLocation() {
        return location;
    }

    @Override
    public void render() {
        // Renders to screen
        System.out.println("Rendered " + this.name);
    }
}

// Proxy class
class ImageProxy implements Image {

    private final String name;
    private BitmapImage image;
    private Point2D location;

    public ImageProxy(String name) {
        this.name = name;
    }

    @Override
    public void setLocation(Point2D point2d) {
        if (image != null) {
            image.setLocation(point2d);
        } else {
            location = point2d;
        }
    }

    @Override
    public Point2D getLocation() {
        if (image != null) {
            return image.getLocation();
        }
        return location;
    }

    @Override
    public void render() {
        if (image == null) {
            image = new BitmapImage(name);
            if (location != null) {
                image.setLocation(location);
            }
        }
        image.render();
    }
}

// Factory to get image objects
class ImageFactory {
    // We'll provide proxy to caller instead of real object
    public static Image getImage(String name) {
        return new ImageProxy(name);
    }
}

// Client class to test the proxy pattern
public class Client {

    public static void main(String[] args) {
        Image img = ImageFactory.getImage("A1.bmp");

        img.setLocation(new Point2D(10, 10));
        System.out.println("Image location: " + img.getLocation());
        System.out.println("Rendering image now...");
        img.render();
    }
}
SHOW OUTPUT
Image location: Point2D [x=10.0, y=10.0]
Rendering image now...
Loaded from disk: A1.bmp
Rendered A1.bmp

---
title: UML Diagram of Static Proxy Design Pattern
---
classDiagram
    class Image {
        + setLocation(Point2D point2d)
        + Point2D getLocation()
        + render()
    }

    class BitmapImage {
        - Point2D location
        - String name
        + BitmapImage(String filename)
        + setLocation(Point2D point2d)
        + getLocation(): Point2D
        + render()
    }

    class ImageProxy {
        - String name
        - BitmapImage image
        - Point2D location
        + ImageProxy(String name)
        + setLocation(Point2D point2d)
        + getLocation(): Point2D
        + render()
    }

    class ImageFactory {
        + getImage(String name): Image
    }

    class Client {
        + main(String[] args)
    }

    Image <|.. BitmapImage : implements
    Image <|.. ImageProxy : implements
    ImageFactory --|> ImageProxy : creates
    Client --> ImageFactory : uses
    ImageProxy "1" --> "0..1" BitmapImage : contains

SHOW CODE: Dynamic Proxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// Point2D class to represent the location of the image
class Point2D {

    private final float x;
    private final float y;

    public Point2D(float x, float y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point2D [x=" + x + ", y=" + y + "]";
    }
}

// Interface implemented by proxy and concrete objects
interface Image {
    void setLocation(Point2D point2d);

    Point2D getLocation();

    void render();
}

// Concrete class providing actual functionality (BitmapImage)
class BitmapImage implements Image {

    private Point2D location;
    private final String name;

    public BitmapImage(String filename) {
        // Loads image from file on disk
        System.out.println("Loaded from disk: " + filename);
        name = filename;
    }

    @Override
    public void setLocation(Point2D point2d) {
        location = point2d;
    }

    @Override
    public Point2D getLocation() {
        return location;
    }

    @Override
    public void render() {
        // Renders to screen
        System.out.println("Rendered " + this.name);
    }
}

// InvocationHandler to implement proxy behavior
class ImageInvocationHandler implements InvocationHandler {

    private final String filename;
    private Point2D location;
    private BitmapImage image;

    private static final Method setLocationMethod;
    private static final Method getLocationMethod;
    private static final Method renderMethod;

    static {
        try {
            setLocationMethod = Image.class.getMethod("setLocation", Point2D.class);
            getLocationMethod = Image.class.getMethod("getLocation");
            renderMethod = Image.class.getMethod("render");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    public ImageInvocationHandler(String filename) {
        this.filename = filename;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        System.out.println("Invoking method: " + method.getName());
        if (method.equals(setLocationMethod)) {
            return handleSetLocation(args);
        } else if (method.equals(getLocationMethod)) {
            return handleGetLocation();
        } else if (method.equals(renderMethod)) {
            return handleRender();
        }
        return null;
    }

    private Object handleRender() {
        if (image == null) {
            image = new BitmapImage(filename);
            if (location != null) {
                image.setLocation(location);
            }
        }
        image.render();
        return null;
    }

    private Point2D handleGetLocation() {
        if (image != null) {
            return image.getLocation();
        } else {
            return this.location;
        }
    }

    private Object handleSetLocation(Object[] args) {
        if (image != null) {
            image.setLocation((Point2D) args[0]);
        } else {
            this.location = (Point2D) args[0];
        }
        return null;
    }
}

// Factory to provide the proxy object
class ImageFactory {
    public static Image getImage(String name) {
        return (Image) Proxy.newProxyInstance(
                ImageFactory.class.getClassLoader(),
                new Class[]{Image.class},
                new ImageInvocationHandler(name)
        );
    }
}

// Client class to test the proxy pattern
public class Client {

    public static void main(String[] args) {
        Image img = ImageFactory.getImage("A.bmp");
        img.setLocation(new Point2D(-10, 0));
        System.out.println(img.getLocation());
        System.out.println("*****************************");
        img.render();
    }
}
SHOW OUTPUT
Invoking method: setLocation
Invoking method: getLocation
Point2D [x=-10.0, y=0.0]
*****************************
Invoking method: render
Loaded from disk: A.bmp
Rendered A.bmp

---
title: UML Diagram of Dynamic Proxy Design Pattern
---
classDiagram
    class Image {
        +setLocation(Point2D point2d)
        +getLocation(): Point2D
        +render()
    }

    class BitmapImage {
        -Point2D location
        -String name
        +BitmapImage(String filename)
        +setLocation(Point2D point2d)
        +getLocation(): Point2D
        +render()
    }

    class ImageInvocationHandler {
        -String filename
        -Point2D location
        -BitmapImage image
        +ImageInvocationHandler(String filename)
        +invoke(Object proxy, Method method, Object[] args)
        -handleRender()
        -handleGetLocation(): Point2D
        -handleSetLocation(Object[] args)
    }

    class ImageFactory {
        +getImage(String name): Image
    }

    class Client {
        +main(String[] args)
    }

    Image <|.. BitmapImage : implements
    Image <|.. ImageInvocationHandler : delegates
    ImageFactory ..> ImageInvocationHandler : creates
    Client ..> ImageFactory : uses
    ImageInvocationHandler "1" -- "1" BitmapImage : image

4. Behavioral Design Patterns

Behavioral Design Patterns focus on the communication and interaction between objects or components in a system.

4.1 Chain of Responsibility

The Chain of Responsibility Design Pattern is a behavioral design pattern that allows a request to be passed along a chain of handlers, where each handler either processes the request or passes it along to the next handler in the chain. The request will continue through the chain until it is handled by an appropriate handler or the end of the chain is reached.

SHOW CODE
import java.time.LocalDate;
import java.time.Period;

// Represents a request in our chain of responsibility
class LeaveApplication {

    public enum Type {Sick, PTO, LOP}

    public enum Status {Pending, Approved, Rejected}

    private final Type type;
    private final LocalDate from;
    private final LocalDate to;
    private String processedBy;
    private Status status;

    public LeaveApplication(Type type, LocalDate from, LocalDate to) {
        this.type = type;
        this.from = from;
        this.to = to;
        this.status = Status.Pending;
    }

    public Type getType() {
        return type;
    }

    public LocalDate getFrom() {
        return from;
    }

    public LocalDate getTo() {
        return to;
    }

    public int getNoOfDays() {
        return Period.between(from, to).getDays();
    }

    public String getProcessedBy() {
        return processedBy;
    }

    public Status getStatus() {
        return status;
    }

    public void approve(String approvalName) {
        this.status = Status.Approved;
        this.processedBy = approvalName;
    }

    public void reject(String approvalName) {
        this.status = Status.Rejected;
        this.processedBy = approvalName;
    }

    public static Builder getBuilder() {
        return new Builder();
    }

    @Override
    public String toString() {
        return type + " leave for " + getNoOfDays() + " day(s) " + status + " by " + processedBy;
    }

    // Builder pattern for LeaveApplication
    public static class Builder {
        private Type type;
        private LocalDate from;
        private LocalDate to;
        private LeaveApplication application;

        private Builder() {
        }

        public Builder withType(Type type) {
            this.type = type;
            return this;
        }

        public Builder from(LocalDate from) {
            this.from = from;
            return this;
        }

        public Builder to(LocalDate to) {
            this.to = to;
            return this;
        }

        public LeaveApplication build() {
            this.application = new LeaveApplication(type, from, to);
            return this.application;
        }

        public LeaveApplication getApplication() {
            return application;
        }
    }
}

// This represents a handler in chain of responsibility
interface LeaveApproval {
    void processLeaveApplication(LeaveApplication application);
    String getApprovalRole();
}

// Abstract handler
abstract class Employee implements LeaveApproval {

    private final String role;
    private final LeaveApproval successor;

    public Employee(String role, LeaveApproval successor) {
        this.role = role;
        this.successor = successor;
    }

    @Override
    public void processLeaveApplication(LeaveApplication application) {
        if (!processRequest(application) && successor != null) {
            successor.processLeaveApplication(application);
        }
    }

    protected abstract boolean processRequest(LeaveApplication application);

    @Override
    public String getApprovalRole() {
        return role;
    }
}

// A concrete handler: Manager
class Manager extends Employee {

    public Manager(LeaveApproval nextApproval) {
        super("Manager", nextApproval);
    }

    @Override
    protected boolean processRequest(LeaveApplication application) {
        switch (application.getType()) {
            case Sick:
                application.approve(getApprovalRole());
                return true;
            case PTO:
                if (application.getNoOfDays() <= 5) {
                    application.approve(getApprovalRole());
                    return true;
                }
        }
        return false;
    }

}

// A concrete handler: ProjectLead
class ProjectLead extends Employee {

    public ProjectLead(LeaveApproval successor) {
        super("Project Lead", successor);
    }

    @Override
    protected boolean processRequest(LeaveApplication application) {
        if (application.getType() == LeaveApplication.Type.Sick) {
            if (application.getNoOfDays() <= 2) {
                application.approve(getApprovalRole());
                return true;
            }
        }
        return false;
    }
}

// A concrete handler: Director
class Director extends Employee {

    public Director(LeaveApproval nextApproval) {
        super("Director", nextApproval);
    }

    @Override
    protected boolean processRequest(LeaveApplication application) {
        if (application.getType() == LeaveApplication.Type.PTO) {
            application.approve(getApprovalRole());
            return true;
        }
        return false;
    }
}

// Client class to test the Chain of Responsibility
public class Client {

    public static void main(String[] args) {
        LeaveApplication application = LeaveApplication.getBuilder()
                .withType(LeaveApplication.Type.PTO)
                .from(LocalDate.now())
                .to(LocalDate.of(2025, 1, 8))
                .build();

        System.out.println(application);
        System.out.println("**************************************************");
        LeaveApproval approval = createChain();
        approval.processLeaveApplication(application);
        System.out.println(application);
    }

    private static LeaveApproval createChain() {
        Director director = new Director(null);
        Manager manager = new Manager(director);
        ProjectLead lead = new ProjectLead(manager);
        return lead;
    }
}
SHOW OUTPUT
PTO leave for 8 day(s) Pending by null
**************************************************
PTO leave for 8 day(s) Approved by Director

---
title: UML Diagram of Chain of Responsibility Design Pattern
---
classDiagram
    class LeaveApproval {
        +void processLeaveApplication(LeaveApplication)
        +String getApprovalRole()
    }

    class Employee {
        +String role
        +LeaveApproval successor
        +Employee(String, LeaveApproval)
        +void processLeaveApplication(LeaveApplication)
        +String getApprovalRole()
        +protected abstract boolean processRequest(LeaveApplication)
    }

    class Manager {
        +Manager(LeaveApproval)
        +boolean processRequest(LeaveApplication)
    }

    class ProjectLead {
        +ProjectLead(LeaveApproval)
        +boolean processRequest(LeaveApplication)
    }

    class Director {
        +Director(LeaveApproval)
        +boolean processRequest(LeaveApplication)
    }

    LeaveApproval <|-- Employee : implements
    Employee <|-- Manager : extends
    Employee <|-- ProjectLead : extends
    Employee <|-- Director : extends
    Employee *--|> LeaveApproval

4.2 Command

The Command Design Pattern is a behavioral design pattern that turns a request into a stand-alone object, allowing the decoupling of the sender of the request from the object that processes the request.

SHOW CODE
import java.util.LinkedList;
import java.util.List;

// Command interface implemented by all concrete command classes
interface Command {
    void execute();
}

// Concrete implementation of Command
class AddMemberCommand implements Command {
    private String emailAddress;
    private String listName;
    private EWSService receiver;

    public AddMemberCommand(String email, String listName, EWSService service) {
        this.emailAddress = email;
        this.listName = listName;
        this.receiver = service;
    }

    @Override
    public void execute() {
        receiver.addMember(emailAddress, listName);
    }
}

// Receiver class that performs the actual action
class EWSService {
    // Add a new member to mailing list
    public void addMember(String contact, String contactGroup) {
        System.out.println("Added " + contact + " to " + contactGroup);
    }

    // Remove member from mailing list
    public void removeMember(String contact, String contactGroup) {
        System.out.println("Removed " + contact + " from " + contactGroup);
    }
}

// Invoker class that executes commands on a separate thread
class MailTasksRunner implements Runnable {
    private Thread runner;
    private List<Command> pendingCommands;
    private volatile boolean stop;

    private static final MailTasksRunner RUNNER = new MailTasksRunner();

    public static final MailTasksRunner getInstance() {
        return RUNNER;
    }

    private MailTasksRunner() {
        pendingCommands = new LinkedList<>();
        runner = new Thread(this);
        runner.start();
    }

    // Run method that takes pending commands and executes them
    @Override
    public void run() {
        while (true) {
            Command cmd = null;
            synchronized (pendingCommands) {
                if (pendingCommands.isEmpty()) {
                    try {
                        pendingCommands.wait();
                    } catch (InterruptedException e) {
                        System.out.println("Runner interrupted");
                        if (stop) {
                            System.out.println("Runner stopping");
                            return;
                        }
                    }
                }
                cmd = pendingCommands.isEmpty() ? null : pendingCommands.remove(0);
            }
            if (cmd == null) {
                return;
            }
            cmd.execute();
        }
    }

    // Adds a command to the pending commands list for later execution
    public void addCommand(Command cmd) {
        synchronized (pendingCommands) {
            pendingCommands.add(cmd);
            pendingCommands.notifyAll();
        }
    }

    // Stops the runner thread
    public void shutdown() {
        this.stop = true;
        this.runner.interrupt();
    }
}

// Client class that demonstrates how the Command pattern works
public class Client {
    public static void main(String[] args) throws InterruptedException {
        EWSService service = new EWSService();

        // Create commands and add them to the runner for execution
        Command c1 = new AddMemberCommand("a@a.com", "spam", service);
        MailTasksRunner.getInstance().addCommand(c1);

        Command c2 = new AddMemberCommand("b@b", "spam", service);
        MailTasksRunner.getInstance().addCommand(c2);

        // Wait for a while to let commands execute
        Thread.sleep(3000);

        // Shutdown the runner
        MailTasksRunner.getInstance().shutdown();
    }
}

---
title: UML Diagram of Command Design Pattern
---
classDiagram

    class Command {
        +execute()
    }

    class AddMemberCommand {
        -String emailAddress
        -String listName
        -EWSService receiver
        +AddMemberCommand(email: String, listName: String, service: EWSService)
        +execute()
    }

    class EWSService {
        +addMember(contact: String, contactGroup: String)
        +removeMember(contact: String, contactGroup: String)
    }

    class MailTasksRunner {
        -Thread runner
        -List~Command~ pendingCommands
        -volatile boolean stop
        -static final MailTasksRunner RUNNER
        +getInstance(): MailTasksRunner
        +addCommand(cmd: Command)
        +shutdown()
        +run()
    }

    class Client {
        +main(args: String[])
    }

    Command <|-- AddMemberCommand
    AddMemberCommand --> EWSService : uses
    MailTasksRunner --> Command : invokes
    Client --> MailTasksRunner : uses

4.3 Interpreter

The Interpreter Design Pattern is a behavioral design pattern used to define a grammatical representation for a language and provide an interpreter to evaluate sentences in that language. It is commonly used to interpret expressions in a simple language or grammar.

SHOW CODE
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.stream.Stream;

// User class
class User {
    private List<String> permissions;
    private String username;

    public User(String username, String... permissions) {
        this.username = username;
        this.permissions = new ArrayList<>();
        Stream.of(permissions).forEach(e -> this.permissions.add(e.toLowerCase()));
    }

    public List<String> getPermissions() {
        return permissions;
    }

    public String getUsername() {
        return username;
    }
}

// Abstract expression
interface PermissionExpression {
    boolean interpret(User user);
}

// Terminal expression
class Permission implements PermissionExpression {
    private String permission;

    public Permission(String permission) {
        this.permission = permission.toLowerCase();
    }

    @Override
    public boolean interpret(User user) {
        return user.getPermissions().contains(permission);
    }

    @Override
    public String toString() {
        return permission;
    }
}

// Non-terminal expression
class AndExpression implements PermissionExpression {
    private PermissionExpression expression1;
    private PermissionExpression expression2;

    public AndExpression(PermissionExpression expression1, PermissionExpression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    @Override
    public boolean interpret(User user) {
        return expression1.interpret(user) && expression2.interpret(user);
    }

    @Override
    public String toString() {
        return expression1 + " AND " + expression2;
    }
}

// Non-terminal expression
class OrExpression implements PermissionExpression {
    private PermissionExpression expression1;
    private PermissionExpression expression2;

    public OrExpression(PermissionExpression one, PermissionExpression two) {
        this.expression1 = one;
        this.expression2 = two;
    }

    @Override
    public boolean interpret(User user) {
        return expression1.interpret(user) || expression2.interpret(user);
    }

    @Override
    public String toString() {
        return expression1 + " OR " + expression2;
    }
}

// Non-terminal expression
class NotExpression implements PermissionExpression {
    private PermissionExpression expression;

    public NotExpression(PermissionExpression expression) {
        this.expression = expression;
    }

    @Override
    public boolean interpret(User user) {
        return !expression.interpret(user);
    }

    @Override
    public String toString() {
        return "NOT " + expression;
    }
}

// Report class
class Report {
    private String name;
    private String permission;

    public Report(String name, String permissions) {
        this.name = name;
        this.permission = permissions;
    }

    public String getName() {
        return name;
    }

    public String getPermission() {
        return permission;
    }
}

// Parses & builds abstract syntax tree
class ExpressionBuilder {
    private Stack<PermissionExpression> permissions = new Stack<>();
    private Stack<String> operators = new Stack<>();

    public PermissionExpression build(Report report) {
        parse(report.getPermission());
        buildExpressions();
        if (permissions.size() > 1 || !operators.isEmpty()) {
            System.out.println("ERROR!");
        }
        return permissions.pop();
    }

    private void parse(String permission) {
        StringTokenizer tokenizer = new StringTokenizer(permission.toLowerCase());
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            switch (token) {
                case "and":
                    operators.push("and");
                    break;
                case "not":
                    operators.push("not");
                    break;
                case "or":
                    operators.push("or");
                    break;
                default:
                    permissions.push(new Permission(token));
                    break;
            }
        }
    }

    private void buildExpressions() {
        while (!operators.isEmpty()) {
            String operator = operators.pop();
            PermissionExpression perm1;
            PermissionExpression perm2;
            PermissionExpression exp;
            switch (operator) {
                case "not":
                    perm1 = permissions.pop();
                    exp = new NotExpression(perm1);
                    break;
                case "and":
                    perm1 = permissions.pop();
                    perm2 = permissions.pop();
                    exp = new AndExpression(perm1, perm2);
                    break;
                case "or":
                    perm1 = permissions.pop();
                    perm2 = permissions.pop();
                    exp = new OrExpression(perm1, perm2);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown operator:" + operator);
            }
            permissions.push(exp);
        }
    }
}

// Client class
public class Client {
    public static void main(String[] args) {
        Report report1 = new Report("Cash flow report", "FINANCE_ADMIN OR ADMIN");
        ExpressionBuilder builder = new ExpressionBuilder();

        PermissionExpression exp = builder.build(report1);
        System.out.println(exp);

        User user1 = new User("Dave", "USER");

        System.out.println("User access report: " + exp.interpret(user1));
    }
}
---
title: UML Diagram of Interpreter Design Pattern
---
classDiagram
    class PermissionExpression {
        + interpret(User user): boolean
    }

    class Permission {
        - String permission
        + Permission(String permission)
        + interpret(User user): boolean
        + toString(): String
    }

    class AndExpression {
        - PermissionExpression expression1
        - PermissionExpression expression2
        + AndExpression(PermissionExpression expression1, PermissionExpression expression2)
        + interpret(User user): boolean
        + toString(): String
    }

    class OrExpression {
        - PermissionExpression expression1
        - PermissionExpression expression2
        + OrExpression(PermissionExpression expression1, PermissionExpression expression2)
        + interpret(User user): boolean
        + toString(): String
    }

    class NotExpression {
        - PermissionExpression expression
        + NotExpression(PermissionExpression expression)
        + interpret(User user): boolean
        + toString(): String
    }

    class ExpressionBuilder {
        - Stack~PermissionExpression~ permissions
        - Stack~String~ operators
        + build(Report report): PermissionExpression
        - parse(String permission)
        - buildExpressions()
    }

    class Client {
        + main(String[] args)
    }

    Permission --|> PermissionExpression
    AndExpression --|> PermissionExpression
    OrExpression --|> PermissionExpression
    NotExpression --|> PermissionExpression
    ExpressionBuilder --> PermissionExpression
    Client --> ExpressionBuilder

4.4 Mediator

4.5 Iterator

4.6 Momento

4.7 Observer

4.8 State

4.9 Strategy

The Strategry Design Pattern is a behavioral design pattern that encapsulates a set of algorithms and allows a client to dynamically choose one at runtime based on the context. This pattern separates the algorithm from the existing code, enabling the addition of new algorithms without modifying the existing code.

SHOW CODE
import java.time.LocalDate;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

// Order class
class Order {

    private final String id;
    private final LocalDate date;
    private final Map<String, Double> items = new HashMap<>();
    private double total;

    public Order(String id) {
        this.id = id;
        this.date = LocalDate.now();
    }

    public String getId() {
        return id;
    }

    public LocalDate getDate() {
        return date;
    }

    public Map<String, Double> getItems() {
        return items;
    }

    public void addItem(String name, double price) {
        items.put(name, price);
        total += price;
    }

    public double getTotal() {
        return total;
    }

    public void setTotal(double total) {
        this.total = total;
    }
}

// Strategy Interface
interface OrderPrinter {
    void print(Collection<Order> orders);
}

// Concrete Strategy 1: Summary Printer
class SummaryPrinter implements OrderPrinter {

    @Override
    public void print(Collection<Order> orders) {
        System.out.println("*************** Summary Report *************");
        Iterator<Order> iterator = orders.iterator();
        double total = 0;
        for (int i = 1; iterator.hasNext(); i++) {
            Order order = iterator.next();
            System.out.println(i + ". " + order.getId() + "\t" + order.getDate() + "\t" + order.getItems().size() + "\t" + order.getTotal());
            total += order.getTotal();
        }
        System.out.println("*******************************************");
        System.out.println("\t\t\t\t  Total " + total);
    }
}

// Concrete Strategy 2: Detail Printer
class DetailPrinter implements OrderPrinter {

    @Override
    public void print(Collection<Order> orders) {
        System.out.println("************* Detail Report ***********");
        Iterator<Order> iter = orders.iterator();
        double total = 0;
        for (int i = 1; iter.hasNext(); i++) {
            double orderTotal = 0;
            Order order = iter.next();
            System.out.println(i + ". " + order.getId() + "\t" + order.getDate());
            for (Map.Entry<String, Double> entry : order.getItems().entrySet()) {
                System.out.println("\t\t" + entry.getKey() + "\t" + entry.getValue());
                orderTotal += entry.getValue();
            }
            System.out.println("----------------------------------------");
            System.out.println("\t\t Total  " + orderTotal);
            System.out.println("----------------------------------------");
            total += orderTotal;
        }
        System.out.println("----------------------------------------");
        System.out.println("\tGrand Total " + total);
    }
}

// PrintService class for context
class PrintService {

    private final OrderPrinter printer;

    public PrintService(OrderPrinter printer) {
        this.printer = printer;
    }

    public void printOrders(Collection<Order> orders) {
        printer.print(orders);
    }
}

// Client class to simulate the creation of orders and print them
public class Main {

    private static final LinkedList<Order> orders = new LinkedList<>();

    public static void main(String[] args) {
        createOrders();
        // Print all orders using DetailPrinter strategy
        PrintService service1 = new PrintService(new DetailPrinter());
        service1.printOrders(orders);

        System.out.println("\n");

        // Print all orders using DetailPrinter strategy
        PrintService service2 = new PrintService(new SummaryPrinter());
        service2.printOrders(orders);
    }

    private static void createOrders() {
        Order o = new Order("100");
        o.addItem("Soda", 2);
        o.addItem("Chips", 10);
        orders.add(o);

        o = new Order("200");
        o.addItem("Cake", 20);
        o.addItem("Cookies", 5);
        orders.add(o);

        o = new Order("300");
        o.addItem("Burger", 8);
        o.addItem("Fries", 5);
        orders.add(o);
    }
}
SHOW OUTPUT
************* Detail Report ***********
1. 100	2024-12-07
		Chips	10.0
		Soda	2.0
----------------------------------------
		 Total  12.0
----------------------------------------
2. 200	2024-12-07
		Cookies	5.0
		Cake	20.0
----------------------------------------
		 Total  25.0
----------------------------------------
3. 300	2024-12-07
		Burger	8.0
		Fries	5.0
----------------------------------------
		 Total  13.0
----------------------------------------
----------------------------------------
	Grand Total 50.0


*************** Summary Report *************
1. 100	2024-12-07	2	12.0
2. 200	2024-12-07	2	25.0
3. 300	2024-12-07	2	13.0
*******************************************
		          Total 50.0

---
title: UML Diagram of Strategy Design Pattern
---
classDiagram
    class Order {
        +String id
        +LocalDate date
        +Map~String, Double~ items
        +double total
        +Order(String id)
        +String getId()
        +LocalDate getDate()
        +Map~String, Double~ getItems()
        +void addItem(String name, double price)
        +double getTotal()
        +void setTotal(double total)
    }

    class OrderPrinter {
        +void print(Collection~Order~ orders)
    }

    class SummaryPrinter {
        +void print(Collection~Order~ orders)
    }

    class DetailPrinter {
        +void print(Collection~Order~ orders)
    }

    class PrintService {
        +OrderPrinter printer
        +PrintService(OrderPrinter printer)
        +void printOrders(Collection~Order~ orders)
    }

    OrderPrinter <|-- SummaryPrinter
    OrderPrinter <|-- DetailPrinter
    PrintService *--> OrderPrinter
    PrintService --> Order

4.10 Template Method

4.11 Visitor

4.12 Null Object