Problem Statement
Design a Splitwise-like system that allows users to split expenses among friends. The system should support equal splitting, exact amounts, and percentage-based splitting. It should also keep track of balances between users and help users understand who owes whom.
Functional Requirements
- Users should be able to create expenses
- Expenses can be split equally, by exact amount, or by percentage
- The system should maintain balances between users
- Users should be able to see how much they owe or are owed
- The system should support multiple groups
- Each group contains multiple users and expenses
Objects Required
- User
- Group
- Expense
- Split
- EqualSplit
- ExactSplit
- PercentSplit
- ExpenseService
- BalanceSheet
User Class
public 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;
}
}
The User class represents a person using the system. It stores basic identity details like userId and name.
The constructor ensures every user is created with valid identity data so we don’t end up with partially defined users in the system.
The getUserId() method is used everywhere we need to uniquely identify a user without directly exposing internal fields.
Split Abstract Class
public abstract class Split {
User user;
double amount;
public Split(User user) {
this.user = user;
}
public abstract double getAmount();
}
The Split class represents how an expense is divided among users. It is kept abstract because different splitting strategies behave differently.
The constructor assigns the user who is part of the split. The actual amount logic is left to child classes because equal, exact, and percentage splits all behave differently.
The getAmount() method forces each subclass to define how much that user owes.
EqualSplit Class
public class EqualSplit extends Split {
private double totalAmount;
private int totalUsers;
public EqualSplit(User user, double totalAmount, int totalUsers) {
super(user);
this.totalAmount = totalAmount;
this.totalUsers = totalUsers;
}
@Override
public double getAmount() {
return totalAmount / totalUsers;
}
}
This class handles equal splitting. The idea is simple: everyone pays the same share.
The constructor takes total amount and number of users involved in the expense.
The getAmount() method divides the total equally. This keeps the logic clean and avoids recalculating it elsewhere.
ExactSplit Class
public class ExactSplit extends Split {
private double exactAmount;
public ExactSplit(User user, double exactAmount) {
super(user);
this.exactAmount = exactAmount;
}
@Override
public double getAmount() {
return exactAmount;
}
}
ExactSplit is used when we already know exactly how much a user should pay.
The constructor stores the exact amount assigned to a user.
The getAmount() method simply returns that value without any calculation.
PercentSplit Class
public class PercentSplit extends Split {
private double percent;
private double totalAmount;
public PercentSplit(User user, double percent, double totalAmount) {
super(user);
this.percent = percent;
this.totalAmount = totalAmount;
}
@Override
public double getAmount() {
return (percent / 100.0) * totalAmount;
}
}
PercentSplit calculates the amount based on percentage contribution.
The constructor stores both percentage and total expense value.
The getAmount() method converts percentage into actual money owed.
Expense Class
import java.util.*;
public class Expense {
private String expenseId;
private double totalAmount;
private User paidBy;
private List splits;
public Expense(String expenseId,
double totalAmount,
User paidBy,
List splits) {
this.expenseId = expenseId;
this.totalAmount = totalAmount;
this.paidBy = paidBy;
this.splits = splits;
}
public List getSplits() {
return splits;
}
public User getPaidBy() {
return paidBy;
}
public double getTotalAmount() {
return totalAmount;
}
}
The Expense class represents a single shared cost.
The constructor binds together who paid, how much was paid, and how it should be split.
getSplits() is used when updating balances between users.
getPaidBy() helps identify who should be credited for the payment.
getTotalAmount() returns the full expense value used for validation and balance updates.
BalanceSheet Class
import java.util.*;
public class BalanceSheet {
private Map balances = new HashMap<>();
public void updateBalance(User paidBy, User owedBy, double amount) {
String key = paidBy.getUserId() + "->" + owedBy.getUserId();
balances.put(key,
balances.getOrDefault(key, 0.0) + amount);
}
public Map getBalances() {
return balances;
}
}
BalanceSheet keeps track of who owes money to whom.
updateBalance() increases or decreases debt between two users whenever an expense is added.
It uses a simple key format (payer → borrower) to store relationships in memory.
getBalances() helps fetch all outstanding balances in the system.
ExpenseService Class
import java.util.*;
public class ExpenseService {
private BalanceSheet balanceSheet = new BalanceSheet();
public void addExpense(Expense expense) {
User paidBy = expense.getPaidBy();
double totalAmount = expense.getTotalAmount();
for (Split split : expense.getSplits()) {
double amount = split.getAmount();
if (!split.user.getUserId()
.equals(paidBy.getUserId())) {
balanceSheet.updateBalance(
paidBy,
split.user,
amount
);
}
}
}
public BalanceSheet getBalanceSheet() {
return balanceSheet;
}
}
This is the core service of the system where expenses are actually processed.
addExpense() takes a new expense and breaks it down into individual splits. Then it updates balances between users one by one.
It avoids updating balance for the person who paid the expense.
getBalanceSheet() simply exposes current balances for reporting.
Main Class
import java.util.*;
public class Main {
public static void main(String[] args) {
User u1 = new User("U1", "A");
User u2 = new User("U2", "B");
User u3 = new User("U3", "C");
Split s1 = new EqualSplit(u1, 300, 3);
Split s2 = new EqualSplit(u2, 300, 3);
Split s3 = new EqualSplit(u3, 300, 3);
Expense expense = new Expense(
"E1",
300,
u1,
Arrays.asList(s1, s2, s3)
);
ExpenseService service = new ExpenseService();
service.addExpense(expense);
System.out.println(service.getBalanceSheet().getBalances());
}
}
The main method simulates a real-world splitwise flow.
We create users, define how the expense should be split, and then pass it to the service.
Finally, the balance sheet is printed to show how money is distributed among users.
Comments
Post a Comment