Loading...

12-Hour Money-Back Guarantee

Design (LLD) Splitwise application - Machine Coding

Design (LLD) Splitwise application  - Machine Coding

Design (LLD) Splitwise application - Machine Coding

23 Oct 20225 min read

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.