This document provides a high-level map of the core building blocks in Splitwise++, how data moves between them, and the constraints that guided the implementation.
| Module | Responsibility |
|---|---|
User |
Immutable value object representing a participant (id + display name). |
Group |
Maintains membership (user ids) and provides membership lookups used by expense validation. |
SplitStrategy hierarchy |
Encapsulates the math for equal/exact/percent distributions; returns per-user deltas. |
SplitStrategyFactory |
Resolves a runtime string to a concrete SplitStrategy implementation. |
Expense |
Records an applied strategy, its parameters (SplitInput), and contextual metadata. |
BalanceSheet |
Aggregates per-user running balances and exposes JSON serialisation helpers. |
SplitwiseManager |
Thread-safe façade that coordinates users, groups, expenses, and persistence. |
ConsoleNotifier |
Minimal observer used to demonstrate the notification extension point. |
CLI (src/main.cpp) |
User-facing loop that translates menu selections into manager calls. |
- The CLI gathers input (group id, payer, amount, strategy type, participants) and requests a strategy instance from the factory.
SplitwiseManager::addExpensevalidates:- the group and payer exist,
- at least one participant was provided, all belong to the group, and the payer is included,
- the strategy pointer is non-null.
- The chosen
SplitStrategycomputes aBalanceSheet::BalanceMapdelta that is applied to the sharedBalanceSheet. - If the amount breaches the configured threshold,
ConsoleNotifieris invoked (no-op by default except for console output).
Saving (saveToJson):
- Acquire the manager mutex to freeze concurrent mutations.
- Serialise users, groups, and expenses via their
toJsonhelpers and dump the snapshot to disk alongside the current balances.
Loading (loadFromJson):
- Lock the mutex and parse the JSON document.
- Validate top-level sections, rehydrate users and groups, and ensure all membership references remain valid.
- Reconstruct expenses by pulling a fresh strategy from the factory, then verifying payer/participants against the owning group.
- Regenerate id counters to keep future inserts monotonic.
- Recompute balances solely from the expense list to guarantee consistency.
SplitwiseManager guards all mutating operations (addUser, addGroup, addExpense, saveToJson, loadFromJson, notifier setters)
with an internal std::mutex. Read-only accessors return references to internal maps but rely on external callers to avoid mutation.
The greedy settle-up algorithm also takes the mutex to snapshot balances safely before deriving settlement transactions.
- Strategies: implement
SplitStrategy::computeSplits, register with the factory, and the CLI automatically accepts the new type. - Notifiers: provide an
INotifierimplementation and callSplitwiseManager::setNotifierto enable richer alerting (e.g. email). - Persistence: the load/save helpers intentionally separate entity serialisation logic, easing alternative backends (e.g. SQLite).
- CLI Enhancements: menu handlers live in
src/main.cppinside a small helper namespace, making it straightforward to add additional commands such as reporting or editing workflows.
tests/strategy_tests.cppensures each strategy enforces its invariants (sum checks, vector lengths) and computes correct deltas.tests/reconciliation_tests.cppexercises the manager end-to-end: expense combinations, persistence round-trip, and settle-up flow.- GitHub Actions (
.github/workflows/cpp.yml) runs the full CMake build + Catch2 test suite to keep regressions out of the main branch.
CLI (main.cpp)
│
└──> SplitwiseManager ───┬──> User / Group registries
├──> Expense ledger ──┬──> SplitStrategy (Equal/Exact/Percent)
│ └──> BalanceSheet (apply deltas)
├──> SplitStrategyFactory (runtime resolution)
└──> Optional INotifier observers