Design (LLD) Splitwise application  - Machine Coding

Design (LLD) Splitwise application - Machine Coding

The Splitwise application involves several features that can be implemented using various design patterns. Below is an outline of the required features, design patterns, and a basic code structure for each.

Features Required:

  1. User Management:

    • Create User

    • Add/Remove Friends

  2. Group Management:

    • Create Group

    • Add/Remove Members

    • Track Group Expenses

  3. Expense Management:

    • Add Expense

    • Split Expense (Equally, Unequally, by Percentage)

    • View Balances

  4. Settlement:

    • Settle Balances

Design Patterns Involved or Used:

  1. Singleton Pattern:

    • Use a singleton pattern for a UserManager to manage user-related operations.
  2. Factory Pattern:

    • Use a factory pattern to create different types of expenses (EqualExpense, UnequalExpense, PercentageExpense).
  3. Observer Pattern:

    • Implement an observer pattern to notify users/groups about changes in expenses or settlements.
  4. Strategy Pattern:

    • Use a strategy pattern for splitting expenses (Equally, Unequally, by Percentage).
  5. Command Pattern:

    • Implement a command pattern to encapsulate different expense operations.
  6. Facade Pattern:

    • Utilize a facade pattern to simplify interactions and provide a unified interface to the system.

Diagrams

Sequence Diagram

Code: Detailed Implementation of Features and Classes

import java.util.*;

// Singleton Pattern
class UserManager {
    private static UserManager instance;
    private Map<String, User> users;

    private UserManager() {
        users = new HashMap<>();
    }

    public static UserManager getInstance() {
        if (instance == null) {
            instance = new UserManager();
        }
        return instance;
    }

    public void addUser(User user) {
        users.put(user.getUserId(), user);
    }

    public User getUser(String userId) {
        return users.get(userId);
    }
}

class User {
    private String userId;
    private String name;

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

    public String getUserId() {
        return userId;
    }

    public String getName() {
        return name;
    }
}

// Factory Pattern
interface ExpenseFactory {
    Expense createExpense(double totalAmount, List<User> participants);
}

class EqualExpenseFactory implements ExpenseFactory {
    public Expense createExpense(double totalAmount, List<User> participants) {
        return new EqualExpense(totalAmount, participants);
    }
}

class UnequalExpenseFactory implements ExpenseFactory {
    public Expense createExpense(double totalAmount, List<User> participants) {
        return new UnequalExpense(totalAmount, participants);
    }
}

// Observer Pattern
interface Observer {
    void update();
}

class ExpenseObserver implements Observer {
    public void update() {
        // Update logic for expenses...
    }
}

// Strategy Pattern
interface SplitStrategy {
    Map<User, Double> splitExpense(double totalAmount, List<User> participants);
}

class EqualSplitStrategy implements SplitStrategy {
    public Map<User, Double> splitExpense(double totalAmount, List<User> participants) {
        Map<User, Double> shares = new HashMap<>();
        double share = totalAmount / participants.size();
        for (User participant : participants) {
            shares.put(participant, share);
        }
        return shares;
    }
}

class UnequalSplitStrategy implements SplitStrategy {
    public Map<User, Double> splitExpense(double totalAmount, List<User> participants) {
        // Custom splitting logic based on user preferences, weights, etc.
        // For simplicity, we'll use equal splitting for demonstration purposes.
        return new EqualSplitStrategy().splitExpense(totalAmount, participants);
    }
}

// Command Pattern
interface ExpenseCommand {
    void execute();
}

class AddExpenseCommand implements ExpenseCommand {
    private Expense expense;

    public AddExpenseCommand(Expense expense) {
        this.expense = expense;
    }

    public void execute() {
        // Execute add expense logic...
        expense.calculateShares();
    }
}

// Facade Pattern
class SplitwiseFacade {
    private UserManager userManager;
    private List<Expense> expenses;
    private List<Observer> observers;

    public SplitwiseFacade() {
        userManager = UserManager.getInstance();
        expenses = new ArrayList<>();
        observers = new ArrayList<>();
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void addUser(User user) {
        userManager.addUser(user);
    }

    public void addEqualExpense(double totalAmount, List<User> participants) {
        ExpenseFactory factory = new EqualExpenseFactory();
        Expense expense = factory.createExpense(totalAmount, participants);
        expenses.add(expense);

        notifyObservers();
    }

    // Other facade methods...

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

// Expense Classes
abstract class Expense {
    protected double totalAmount;
    protected List<User> participants;
    protected Map<User, Double> shares;
    protected SplitStrategy splitStrategy;

    public Expense(double totalAmount, List<User> participants, SplitStrategy splitStrategy) {
        this.totalAmount = totalAmount;
        this.participants = participants;
        this.splitStrategy = splitStrategy;
    }

    public abstract void calculateShares();
}

class EqualExpense extends Expense {
    public EqualExpense(double totalAmount, List<User> participants) {
        super(totalAmount, participants, new EqualSplitStrategy());
    }

    public void calculateShares() {
        shares = splitStrategy.splitExpense(totalAmount, participants);
    }
}

class UnequalExpense extends Expense {
    public UnequalExpense(double totalAmount, List<User> participants) {
        super(totalAmount, participants, new UnequalSplitStrategy());
    }

    public void calculateShares() {
        shares = splitStrategy.splitExpense(totalAmount, participants);
    }
}

// Main Class
public class SplitwiseApp {
    public static void main(String[] args) {
        SplitwiseFacade splitwise = new SplitwiseFacade();

        User user1 = new User("1", "Alice");
        User user2 = new User("2", "Bob");

        splitwise.addUser(user1);
        splitwise.addUser(user2);

        splitwise.addObserver(new ExpenseObserver());

        splitwise.addEqualExpense(100.0, Arrays.asList(user1, user2));

        // Additional operations...
    }
}

This is a comprehensive example of a Splitwise application, considering various design patterns for modularity and extensibility. Note that this is a simplified version, and in a real-world scenario, you might need more features, error handling, and optimization.

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!