Skip to content

Commit 84d4330

Browse files
committed
feat: implement bank account system with auditing and transaction logging
1 parent 1c93014 commit 84d4330

File tree

9 files changed

+527
-126
lines changed

9 files changed

+527
-126
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.codedifferently.lesson17.bank;
2+
3+
import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException;
4+
import java.util.Set;
5+
6+
/**
7+
* Represents a bank account interface that defines common account operations. This interface
8+
* follows the Interface Segregation Principle by providing only the essential operations that all
9+
* account types must support.
10+
*/
11+
public interface Account {
12+
13+
/**
14+
* Gets the account number.
15+
*
16+
* @return The account number.
17+
*/
18+
String getAccountNumber();
19+
20+
/**
21+
* Gets the owners of the account.
22+
*
23+
* @return The owners of the account.
24+
*/
25+
Set<Customer> getOwners();
26+
27+
/**
28+
* Deposits funds into the account.
29+
*
30+
* @param amount The amount to deposit.
31+
* @throws IllegalStateException if the account is closed or amount is invalid.
32+
*/
33+
void deposit(double amount) throws IllegalStateException;
34+
35+
/**
36+
* Withdraws funds from the account.
37+
*
38+
* @param amount The amount to withdraw.
39+
* @throws InsufficientFundsException if insufficient funds available.
40+
* @throws IllegalStateException if the account is closed or amount is invalid.
41+
*/
42+
void withdraw(double amount) throws InsufficientFundsException, IllegalStateException;
43+
44+
/**
45+
* Gets the balance of the account.
46+
*
47+
* @return The current account balance.
48+
*/
49+
double getBalance();
50+
51+
/**
52+
* Closes the account.
53+
*
54+
* @throws IllegalStateException if the account cannot be closed.
55+
*/
56+
void closeAccount() throws IllegalStateException;
57+
58+
/**
59+
* Checks if the account is closed.
60+
*
61+
* @return True if the account is closed, otherwise false.
62+
*/
63+
boolean isClosed();
64+
}

lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,44 @@
11
package com.codedifferently.lesson17.bank;
22

3+
import com.codedifferently.lesson17.bank.audit.TransactionObserver;
34
import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException;
5+
import java.util.ArrayList;
46
import java.util.HashMap;
7+
import java.util.List;
58
import java.util.Map;
69
import java.util.Set;
710
import java.util.UUID;
811

9-
/** Represents a bank ATM. */
12+
/** Represents a bank ATM that supports multiple account types and audit logging. */
1013
public class BankAtm {
1114

1215
private final Map<UUID, Customer> customerById = new HashMap<>();
13-
private final Map<String, CheckingAccount> accountByNumber = new HashMap<>();
16+
private final Map<String, Account> accountByNumber = new HashMap<>();
17+
private final List<TransactionObserver> observers = new ArrayList<>();
18+
19+
/** Adds a transaction observer for audit logging. */
20+
public void addObserver(TransactionObserver observer) {
21+
if (observer != null && !observers.contains(observer)) {
22+
observers.add(observer);
23+
}
24+
}
25+
26+
/** Removes a transaction observer. */
27+
public void removeObserver(TransactionObserver observer) {
28+
observers.remove(observer);
29+
}
30+
31+
/** Notifies all observers about a transaction. */
32+
private void notifyObservers(
33+
String transactionType, double amount, String accountNumber, String description) {
34+
for (TransactionObserver observer : observers) {
35+
observer.onTransaction(transactionType, amount, accountNumber, description);
36+
}
37+
}
1438

1539
/**
16-
* Adds a checking account to the bank.
40+
* Adds an account to the bank. This method now accepts any Account type, supporting both
41+
* CheckingAccount and SavingsAccount while maintaining backward compatibility.
1742
*
1843
* @param account The account to add.
1944
*/
@@ -27,6 +52,22 @@ public void addAccount(CheckingAccount account) {
2752
});
2853
}
2954

55+
/**
56+
* Adds a savings account to the bank. This overloaded method allows adding SavingsAccount objects
57+
* while maintaining the same public interface.
58+
*
59+
* @param account The savings account to add.
60+
*/
61+
public void addAccount(SavingsAccount account) {
62+
accountByNumber.put(account.getAccountNumber(), account);
63+
account
64+
.getOwners()
65+
.forEach(
66+
owner -> {
67+
customerById.put(owner.getId(), owner);
68+
});
69+
}
70+
3071
/**
3172
* Finds all accounts owned by a customer.
3273
*
@@ -40,36 +81,68 @@ public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
4081
}
4182

4283
/**
43-
* Deposits funds into an account.
84+
* Deposits funds into an account using cash.
4485
*
4586
* @param accountNumber The account number.
4687
* @param amount The amount to deposit.
4788
*/
4889
public void depositFunds(String accountNumber, double amount) {
49-
CheckingAccount account = getAccountOrThrow(accountNumber);
50-
account.deposit(amount);
90+
try {
91+
Account account = getAccountOrThrow(accountNumber);
92+
account.deposit(amount);
93+
notifyObservers("CREDIT", amount, accountNumber, "Cash deposit");
94+
} catch (Exception e) {
95+
notifyObservers("CREDIT", amount, accountNumber, "Failed: " + e.getMessage());
96+
throw e;
97+
}
5198
}
5299

53100
/**
54-
* Deposits funds into an account using a check.
101+
* Deposits funds into an account using a check. This method validates that the account supports
102+
* check transactions.
55103
*
56104
* @param accountNumber The account number.
57105
* @param check The check to deposit.
106+
* @throws IllegalStateException if the account doesn't support check transactions.
58107
*/
59108
public void depositFunds(String accountNumber, Check check) {
60-
CheckingAccount account = getAccountOrThrow(accountNumber);
61-
check.depositFunds(account);
109+
try {
110+
Account account = getAccountOrThrow(accountNumber);
111+
112+
// Check if account supports check transactions (Open/Closed Principle)
113+
if (account instanceof SavingsAccount savingsAccount
114+
&& !savingsAccount.supportsCheckTransactions()) {
115+
throw new IllegalStateException("Savings accounts do not support check transactions");
116+
}
117+
118+
// For checking accounts or accounts that support checks, proceed with deposit
119+
if (account instanceof CheckingAccount checkingAccount) {
120+
check.depositFunds(checkingAccount);
121+
notifyObservers("CREDIT", 0.0, accountNumber, "Check deposit: " + check.toString());
122+
} else {
123+
throw new IllegalStateException("Account type does not support check deposits");
124+
}
125+
} catch (Exception e) {
126+
notifyObservers("CREDIT", 0.0, accountNumber, "Failed check deposit: " + e.getMessage());
127+
throw e;
128+
}
62129
}
63130

64131
/**
65-
* Withdraws funds from an account.
132+
* Withdraws funds from an account using cash.
66133
*
67-
* @param accountNumber
68-
* @param amount
134+
* @param accountNumber The account number.
135+
* @param amount The amount to withdraw.
69136
*/
70137
public void withdrawFunds(String accountNumber, double amount) {
71-
CheckingAccount account = getAccountOrThrow(accountNumber);
72-
account.withdraw(amount);
138+
try {
139+
Account account = getAccountOrThrow(accountNumber);
140+
account.withdraw(amount);
141+
notifyObservers("DEBIT", amount, accountNumber, "Cash withdrawal");
142+
} catch (Exception e) {
143+
notifyObservers("DEBIT", amount, accountNumber, "Failed: " + e.getMessage());
144+
throw e;
145+
}
73146
}
74147

75148
/**
@@ -78,8 +151,8 @@ public void withdrawFunds(String accountNumber, double amount) {
78151
* @param accountNumber The account number.
79152
* @return The account.
80153
*/
81-
private CheckingAccount getAccountOrThrow(String accountNumber) {
82-
CheckingAccount account = accountByNumber.get(accountNumber);
154+
private Account getAccountOrThrow(String accountNumber) {
155+
Account account = accountByNumber.get(accountNumber);
83156
if (account == null || account.isClosed()) {
84157
throw new AccountNotFoundException("Account not found");
85158
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.codedifferently.lesson17.bank;
2+
3+
import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException;
4+
import java.util.Set;
5+
6+
/**
7+
* Abstract base class for all bank accounts that provides common functionality. This class follows
8+
* the Template Method pattern and implements shared behavior while allowing subclasses to define
9+
* specific account rules.
10+
*/
11+
public abstract class BaseAccount implements Account {
12+
13+
protected final Set<Customer> owners;
14+
protected final String accountNumber;
15+
protected double balance;
16+
protected boolean isActive;
17+
18+
/**
19+
* Creates a new base account.
20+
*
21+
* @param accountNumber The account number.
22+
* @param owners The owners of the account.
23+
* @param initialBalance The initial balance of the account.
24+
*/
25+
protected BaseAccount(String accountNumber, Set<Customer> owners, double initialBalance) {
26+
this.accountNumber = accountNumber;
27+
this.owners = owners;
28+
this.balance = initialBalance;
29+
this.isActive = true;
30+
}
31+
32+
@Override
33+
public String getAccountNumber() {
34+
return accountNumber;
35+
}
36+
37+
@Override
38+
public Set<Customer> getOwners() {
39+
return owners;
40+
}
41+
42+
@Override
43+
public void deposit(double amount) throws IllegalStateException {
44+
if (isClosed()) {
45+
throw new IllegalStateException("Cannot deposit to a closed account");
46+
}
47+
if (amount <= 0) {
48+
throw new IllegalArgumentException("Deposit amount must be positive");
49+
}
50+
balance += amount;
51+
}
52+
53+
@Override
54+
public void withdraw(double amount) throws InsufficientFundsException, IllegalStateException {
55+
if (isClosed()) {
56+
throw new IllegalStateException("Cannot withdraw from a closed account");
57+
}
58+
if (amount <= 0) {
59+
throw new IllegalStateException("Withdrawal amount must be positive");
60+
}
61+
if (balance < amount) {
62+
throw new InsufficientFundsException("Account does not have enough funds for withdrawal");
63+
}
64+
65+
// Template method - allows subclasses to add additional withdrawal validation
66+
if (!canWithdraw(amount)) {
67+
throw new IllegalStateException("Withdrawal not allowed for this account type");
68+
}
69+
70+
balance -= amount;
71+
}
72+
73+
@Override
74+
public double getBalance() {
75+
return balance;
76+
}
77+
78+
@Override
79+
public void closeAccount() throws IllegalStateException {
80+
if (balance > 0) {
81+
throw new IllegalStateException("Cannot close account with a positive balance");
82+
}
83+
isActive = false;
84+
}
85+
86+
@Override
87+
public boolean isClosed() {
88+
return !isActive;
89+
}
90+
91+
/**
92+
* Template method that allows subclasses to add additional withdrawal validation. This follows
93+
* the Open/Closed Principle - open for extension, closed for modification.
94+
*
95+
* @param amount The amount to withdraw.
96+
* @return True if the withdrawal is allowed, false otherwise.
97+
*/
98+
protected abstract boolean canWithdraw(double amount);
99+
100+
@Override
101+
public int hashCode() {
102+
return accountNumber.hashCode();
103+
}
104+
105+
@Override
106+
public boolean equals(Object obj) {
107+
if (obj instanceof BaseAccount other) {
108+
return accountNumber.equals(other.accountNumber);
109+
}
110+
return false;
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return getClass().getSimpleName()
116+
+ "{"
117+
+ "accountNumber='"
118+
+ accountNumber
119+
+ '\''
120+
+ ", balance="
121+
+ balance
122+
+ ", isActive="
123+
+ isActive
124+
+ '}';
125+
}
126+
}

0 commit comments

Comments
 (0)