Designing Splitwise


Requirements
Design Components:
Code Implementation:
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

// Abstract class for different split methods
abstract class Split {
    private User user;
    private double amount;

    public Split(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}
// Equal Split
class EqualSplit extends Split {
    public EqualSplit(User user) {
        super(user);
    }
}
// Percent Split
class PercentSplit extends Split {
    private double percentage;

    public PercentSplit(User user, double percentage) {
        super(user);
        this.percentage = percentage;
    }

    public double getPercentage() {
        return percentage;
    }
}
// Exact Split
class ExactSplit extends Split {
    public ExactSplit(User user) {
        super(user);
    }
}
// User class
class User {
    private String id;
    private String name;
    private String email;
    private Map<String, Double> balances;

    public User(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.balances = new ConcurrentHashMap<>();
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Map<String, Double> getBalances() {
        return balances;
    }
}
// Expense class
class Expense {
    private String id;
    private double amount;
    private String description;
    private User paidBy;
    private List<Split> splits;

    public Expense(String id, double amount, String description, User paidBy, List<Split> splits) {
        this.id = id;
        this.amount = amount;
        this.description = description;
        this.paidBy = paidBy;
        this.splits = splits;
    }

    public double getAmount() {
        return amount;
    }

    public List<Split> getSplits() {
        return splits;
    }

    public User getPaidBy() {
        return paidBy;
    }
}
// Group class
class Group {
    private String id;
    private String name;
    private List<User> members;
    private List<Expense> expenses;

    public Group(String id, String name) {
        this.id = id;
        this.name = name;
        this.members = new CopyOnWriteArrayList<>();
        this.expenses = new CopyOnWriteArrayList<>();
    }

    public List<User> getMembers() {
        return members;
    }

    public List<Expense> getExpenses() {
        return expenses;
    }

    public void addMember(User user) {
        members.add(user);
    }

    public void addExpense(Expense expense) {
        expenses.add(expense);
    }
}
// Splitwise Service
class SplitwiseService {
    private static SplitwiseService instance;
    private Map<String, User> users;
    private Map<String, Group> groups;

    private SplitwiseService() {
        users = new ConcurrentHashMap<>();
        groups = new ConcurrentHashMap<>();
    }

    public static SplitwiseService getInstance() {
        if (instance == null) {
            synchronized (SplitwiseService.class) {
                if (instance == null) {
                    instance = new SplitwiseService();
                }
            }
        }
        return instance;
    }

    public User addUser(String id, String name, String email) {
        User user = new User(id, name, email);
        users.put(id, user);
        return user;
    }

    public Group addGroup(String id, String name) {
        Group group = new Group(id, name);
        groups.put(id, group);
        return group;
    }

    public void addExpense(Group group, Expense expense) {
        group.addExpense(expense);
        updateBalances(expense);
    }

    private void updateBalances(Expense expense) {
        User paidBy = expense.getPaidBy();
        double amount = expense.getAmount();
        List<Split> splits = expense.getSplits();

        for (Split split : splits) {
            User owedBy = split.getUser();
            double splitAmount = split.getAmount();
            paidBy.getBalances().put(owedBy.getId(),
                    paidBy.getBalances().getOrDefault(owedBy.getId(), 0.0) + splitAmount);
            owedBy.getBalances().put(paidBy.getId(),
                    owedBy.getBalances().getOrDefault(paidBy.getId(), 0.0) - splitAmount);
        }
    }

    public void settleBalance(User user1, User user2) {
        double balance = user1.getBalances().getOrDefault(user2.getId(), 0.0);
        user1.getBalances().put(user2.getId(), 0.0);
        user2.getBalances().put(user1.getId(), 0.0);
        System.out.println("Settled balance of " + balance + " between " + user1.getName() + " and " + user2.getName());
    }

    public void printBalances(User user) {
        System.out.println("Balances for " + user.getName() + ":");
        for (Map.Entry<String, Double> entry : user.getBalances().entrySet()) {
            System.out.println("  Owes " + entry.getKey() + ": " + entry.getValue());
        }
    }
}
// Demo Class
public class SplitwiseDemo {
    public static void main(String[] args) {
        SplitwiseService service = SplitwiseService.getInstance();

        User u1 = service.addUser("1", "Alice", "alice@example.com");
        User u2 = service.addUser("2", "Bob", "bob@example.com");
        User u3 = service.addUser("3", "Charlie", "charlie@example.com");

        Group group = service.addGroup("g1", "Friends");
        group.addMember(u1);
        group.addMember(u2);
        group.addMember(u3);

        List<Split> splits = Arrays.asList(
                new EqualSplit(u1),
                new EqualSplit(u2),
                new EqualSplit(u3)
        );
        splits.forEach(split -> split.setAmount(100.0 / splits.size()));

        Expense expense = new Expense("e1", 100.0, "Dinner", u1, splits);
        service.addExpense(group, expense);

        service.printBalances(u1);
        service.printBalances(u2);
        service.printBalances(u3);

        service.settleBalance(u2, u1);
    }
}
Input: Output:
Balances for Alice:
  Owes Bob: 33.33333333333333
  Owes Charlie: 33.33333333333333
Balances for Bob:
  Owes Alice: -33.33333333333333
Balances for Charlie:
  Owes Alice: -33.33333333333333
Settled balance of 33.33333333333333 between Bob and Alice
    

Explanation of the Code:
Design Considerations:
Improvements: