Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.codedifferently.lesson17.bank;

import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException;
import java.util.Set;

/**
* Represents a bank account interface that defines common account operations. This interface
* follows the Interface Segregation Principle by providing only the essential operations that all
* account types must support.
*/
public interface Account {

/**
* Gets the account number.
*
* @return The account number.
*/
String getAccountNumber();

/**
* Gets the owners of the account.
*
* @return The owners of the account.
*/
Set<Customer> getOwners();

/**
* Deposits funds into the account.
*
* @param amount The amount to deposit.
* @throws IllegalStateException if the account is closed or amount is invalid.
*/
void deposit(double amount) throws IllegalStateException;

/**
* Withdraws funds from the account.
*
* @param amount The amount to withdraw.
* @throws InsufficientFundsException if insufficient funds available.
* @throws IllegalStateException if the account is closed or amount is invalid.
*/
void withdraw(double amount) throws InsufficientFundsException, IllegalStateException;

/**
* Gets the balance of the account.
*
* @return The current account balance.
*/
double getBalance();

/**
* Closes the account.
*
* @throws IllegalStateException if the account cannot be closed.
*/
void closeAccount() throws IllegalStateException;

/**
* Checks if the account is closed.
*
* @return True if the account is closed, otherwise false.
*/
boolean isClosed();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
package com.codedifferently.lesson17.bank;

import com.codedifferently.lesson17.bank.audit.TransactionObserver;
import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/** Represents a bank ATM. */
/** Represents a bank ATM that supports multiple account types and audit logging. */
public class BankAtm {

private final Map<UUID, Customer> customerById = new HashMap<>();
private final Map<String, CheckingAccount> accountByNumber = new HashMap<>();
private final Map<String, Account> accountByNumber = new HashMap<>();
private final List<TransactionObserver> observers = new ArrayList<>();

/** Adds a transaction observer for audit logging. */
public void addObserver(TransactionObserver observer) {
if (observer != null && !observers.contains(observer)) {
observers.add(observer);
}
}

/** Removes a transaction observer. */
public void removeObserver(TransactionObserver observer) {
observers.remove(observer);
}

/** Notifies all observers about a transaction. */
private void notifyObservers(
String transactionType, double amount, String accountNumber, String description) {
for (TransactionObserver observer : observers) {
observer.onTransaction(transactionType, amount, accountNumber, description);
}
}

/**
* Adds a checking account to the bank.
* Adds an account to the bank. This method now accepts any Account type, supporting both
* CheckingAccount and SavingsAccount while maintaining backward compatibility.
*
* @param account The account to add.
*/
Expand All @@ -27,6 +52,22 @@ public void addAccount(CheckingAccount account) {
});
}

/**
* Adds a savings account to the bank. This overloaded method allows adding SavingsAccount objects
* while maintaining the same public interface.
*
* @param account The savings account to add.
*/
public void addAccount(SavingsAccount account) {
accountByNumber.put(account.getAccountNumber(), account);
account
.getOwners()
.forEach(
owner -> {
customerById.put(owner.getId(), owner);
});
}

/**
* Finds all accounts owned by a customer.
*
Expand All @@ -40,36 +81,68 @@ public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
}

/**
* Deposits funds into an account.
* Deposits funds into an account using cash.
*
* @param accountNumber The account number.
* @param amount The amount to deposit.
*/
public void depositFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.deposit(amount);
try {
Account account = getAccountOrThrow(accountNumber);
account.deposit(amount);
notifyObservers("CREDIT", amount, accountNumber, "Cash deposit");
} catch (Exception e) {
notifyObservers("CREDIT", amount, accountNumber, "Failed: " + e.getMessage());
throw e;
}
}

/**
* Deposits funds into an account using a check.
* Deposits funds into an account using a check. This method validates that the account supports
* check transactions.
*
* @param accountNumber The account number.
* @param check The check to deposit.
* @throws IllegalStateException if the account doesn't support check transactions.
*/
public void depositFunds(String accountNumber, Check check) {
CheckingAccount account = getAccountOrThrow(accountNumber);
check.depositFunds(account);
try {
Account account = getAccountOrThrow(accountNumber);

// Check if account supports check transactions (Open/Closed Principle)
if (account instanceof SavingsAccount savingsAccount
&& !savingsAccount.supportsCheckTransactions()) {
throw new IllegalStateException("Savings accounts do not support check transactions");
}

// For checking accounts or accounts that support checks, proceed with deposit
if (account instanceof CheckingAccount checkingAccount) {
check.depositFunds(checkingAccount);
notifyObservers("CREDIT", 0.0, accountNumber, "Check deposit: " + check.toString());
} else {
throw new IllegalStateException("Account type does not support check deposits");
}
} catch (Exception e) {
notifyObservers("CREDIT", 0.0, accountNumber, "Failed check deposit: " + e.getMessage());
throw e;
}
}

/**
* Withdraws funds from an account.
* Withdraws funds from an account using cash.
*
* @param accountNumber
* @param amount
* @param accountNumber The account number.
* @param amount The amount to withdraw.
*/
public void withdrawFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.withdraw(amount);
try {
Account account = getAccountOrThrow(accountNumber);
account.withdraw(amount);
notifyObservers("DEBIT", amount, accountNumber, "Cash withdrawal");
} catch (Exception e) {
notifyObservers("DEBIT", amount, accountNumber, "Failed: " + e.getMessage());
throw e;
}
}

/**
Expand All @@ -78,8 +151,8 @@ public void withdrawFunds(String accountNumber, double amount) {
* @param accountNumber The account number.
* @return The account.
*/
private CheckingAccount getAccountOrThrow(String accountNumber) {
CheckingAccount account = accountByNumber.get(accountNumber);
private Account getAccountOrThrow(String accountNumber) {
Account account = accountByNumber.get(accountNumber);
if (account == null || account.isClosed()) {
throw new AccountNotFoundException("Account not found");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.codedifferently.lesson17.bank;

import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException;
import java.util.Set;

/**
* Abstract base class for all bank accounts that provides common functionality. This class follows
* the Template Method pattern and implements shared behavior while allowing subclasses to define
* specific account rules.
*/
public abstract class BaseAccount implements Account {

protected final Set<Customer> owners;
protected final String accountNumber;
protected double balance;
protected boolean isActive;

/**
* Creates a new base account.
*
* @param accountNumber The account number.
* @param owners The owners of the account.
* @param initialBalance The initial balance of the account.
*/
protected BaseAccount(String accountNumber, Set<Customer> owners, double initialBalance) {
this.accountNumber = accountNumber;
this.owners = owners;
this.balance = initialBalance;
this.isActive = true;
}

@Override
public String getAccountNumber() {
return accountNumber;
}

@Override
public Set<Customer> getOwners() {
return owners;
}

@Override
public void deposit(double amount) throws IllegalStateException {
if (isClosed()) {
throw new IllegalStateException("Cannot deposit to a closed account");
}
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
balance += amount;
}

@Override
public void withdraw(double amount) throws InsufficientFundsException, IllegalStateException {
if (isClosed()) {
throw new IllegalStateException("Cannot withdraw from a closed account");
}
if (amount <= 0) {
throw new IllegalStateException("Withdrawal amount must be positive");
}
if (balance < amount) {
throw new InsufficientFundsException("Account does not have enough funds for withdrawal");
}

// Template method - allows subclasses to add additional withdrawal validation
if (!canWithdraw(amount)) {
throw new IllegalStateException("Withdrawal not allowed for this account type");
}

balance -= amount;
}

@Override
public double getBalance() {
return balance;
}

@Override
public void closeAccount() throws IllegalStateException {
if (balance > 0) {
throw new IllegalStateException("Cannot close account with a positive balance");
}
isActive = false;
}

@Override
public boolean isClosed() {
return !isActive;
}

/**
* Template method that allows subclasses to add additional withdrawal validation. This follows
* the Open/Closed Principle - open for extension, closed for modification.
*
* @param amount The amount to withdraw.
* @return True if the withdrawal is allowed, false otherwise.
*/
protected abstract boolean canWithdraw(double amount);

@Override
public int hashCode() {
return accountNumber.hashCode();
}

@Override
public boolean equals(Object obj) {
if (obj instanceof BaseAccount other) {
return accountNumber.equals(other.accountNumber);
}
return false;
}

@Override
public String toString() {
return getClass().getSimpleName()
+ "{"
+ "accountNumber='"
+ accountNumber
+ '\''
+ ", balance="
+ balance
+ ", isActive="
+ isActive
+ '}';
}
}
Loading
Loading