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...
    }
}

🔴 Drawbacks and Issues

⚠️ 1️⃣ Concurrency Issues in Expense Splitting

Problem:

  • Multiple users can add expenses at the same time, leading to race conditions when updating balances.

  • No locking mechanism ensures the correctness of transactions.

Solution: Will be covered in our premium course.

⚠️ 2️⃣ Inefficient Expense Splitting (O(N²) Complexity)

Problem:

  • Splitting expenses iterates over all participants every time, making it O(N²) in worst case.

  • Not optimized for large groups with many users.

Solution: Will be covered in our premium course.

⚠️ 3️⃣ Lack of Transaction Rollback in Case of Failure

Problem:

  • If a split transaction fails mid-way, some users will have incorrect balances.

  • There is no rollback mechanism to revert incomplete updates.

Solution: Will be covered in our premium course.

⚠️ 4️⃣ Scalability Issues with Large Groups

Problem:

  • Large groups (100+ users) slow down operations due to frequent balance calculations.

  • Every expense update triggers multiple notifications, leading to performance bottlenecks.

Solution: Will be covered in our premium course.

⚠️ 5️⃣ No Notification Throttling in Observer Pattern

Problem:

  • Each expense update notifies all observers immediately, leading to notification overload.

  • High-frequency transactions (e.g., many small expenses) result in notification spam.

Solution: Will be covered in our premium course.