Lab#SE01-1: Maven/Gradle Person and Account

Java SE Lab

javase
lab
composition
singleton
Java SE Lab 01
Author

albertprofe

Published

Tuesday, June 1, 2021

Modified

Tuesday, September 26, 2023

1 Overview

Create a Maven/Gradle Java SE Project with three classes and Junit to test objects and operations.


💻 Lab#SE01-1: Maven/Gradle Person and Account tested by JUnit


Context

In order to complete this project, you will need to have a basic understanding of the Java programming language, as well as some familiarity with Maven or Gradle for managing dependencies and building the project.

Overall, this project will provide an opportunity for you to learn and apply the basics of Java programming, as well as gain experience with Maven or Gradle, JUnit, user input via the console and some discussion about composition and how classes work.

Additionally, you will need to have in mind that this project would center around a banking point of view.

By completing this project, you will have a starting foundation in these technologies and be able to build more complex and sophisticated Java applications in the future. You may go to Lab 2 (go Lab#SE01-2)

Goal

The goal of this project is to create three classes in Java (Person, Account and Manager) that implement different algorithms or data structures, and to test them using JUnit.

These classes could include, for example, creating new objects, a data structure for storing and manipulating data (List), or a utility class for performing common operations (static).

Tasks

The tasks involved in this project include:

  1. Decide where your project will weight: Person or Account.
  2. Creating a new Maven or Gradle project and setting up the project structure.
  3. Modifying the project’s pom.xml or build.gradle file to import the necessary dependencies, including JUnit for testing.
  4. Implementing the three required classes in Java, using appropriate algorithms and data structures.
  5. Implementing as well two basic pattern-designs: singleton and composition.
  6. Writing JUnit tests to verify that the classes work as expected.

You may attach the JUnit Test HTML results to documentation.

Optional

  1. As an optional task, you could also consider allowing the user to input data via the console, rather than using hard-coded test data in your JUnit tests.

    This would allow you to test the classes with a variety of different input data, and to interact with the classes in a more dynamic way.

  2. After mplementing two basic pattern-designs: singleton, composition your may think about factory.

2 Solving discussion

2.1 Base Classes

Here, the Person class represents a person with a name, address and others. In the same way, Account class is a bank account. The AccountManager class contains static methods to perform withdrawal, transfer, and change pin operations on a Person and Account object.

public class Person {
  private String name;
  private String address;
  // Other properties for a Person...

  public Person(String name, String address) {
    this.name = name;
    this.address = address;
  }

  // Getters and setters for Person properties...
}


public class Account {
  private String accountNumber;
  private String pin;
  private double balance;
  // Other properties for an Account...

  public Account(String accountNumber, String pin, double balance) {
    this.accountNumber = accountNumber;
    this.pin = pin;
    this.balance = balance;
  }

  // Getters and setters for Account properties...
}

public class AccountManager {
    public static boolean withdrawal(Person person, double amount) {
        if (amount > 0 && amount <= person.getBalance()) {
            person.setBalance(person.getBalance() - amount);
            return true;
        }
        return false;
    }

    public static boolean transfer(Person sender, Person receiver, double amount) {
        if (amount > 0 && amount <= sender.getBalance()) {
            sender.setBalance(sender.getBalance() - amount);
            receiver.setBalance(receiver.getBalance() + amount);
            return true;
        }
        return false;
    }

    public static boolean changePin(Person person, String oldPin, String newPin) {
        if (person.getPin().equals(oldPin)) {
            person.setPin(newPin);
            return true;
        }
        return false;
    }
}

2.2 Person has Account

public class Person {
  private String name;
  private String surname;
  private int age;
  private Account account;

  public Person(String name, String surname, int age, Account account) {
    this.name = name;
    this.surname = surname;
    this.age = age;
    this.account = account;
  }

  public Account getAccount() {
    return this.account;
  }

  public void setAccount(Account account) {
    this.account = account;
  }
   // Getters and setters for Account properties...
}

classDiagram

class Person {
  -name: String
  -surname: String
  -age: int
  -account: Account
}

class Account {
  -accountNumber: String
  -pin: int
  -balance: double
}

Person *-- Account

sequenceDiagram

participant AccountManager as AccountManager
participant Account as Account
participant Person as Person

AccountManager->>Person: transfer(amount, sender, receiver)
Person->>Account: updateBalance(-amount, sender)
Person->>Account: updateBalance(-amount, receiver)
Account-->>Person: transferDone(receipt)
Person-->>AccountManager: transferDone(receipt)

2.3 Approach #1: Singleton

To use the Singleton design pattern with a Person and Account class, you could create a singleton AccountManager class that manages the creation and operations of the Person and Account objects.

The AccountManager class would have a private constructor, to prevent multiple instances from being created, and a static getInstance method that returns the singleton instance of the class.

The AccountManager class would then have methods for performing various operations on the Person and Account objects, such as transferring money between accounts, withdrawing money from an account, or changing the PIN for an account.

These methods would be implemented using the Person and Account classes, and would be accessible to other classes through the singleton AccountManager instance.

For example, you could define the AccountManager, Person, and Account classes as follows:

public class AccountManager {
  private static AccountManager instance;
  private Person person;
  private Account account;

  private AccountManager() {
    // Private constructor to prevent multiple instances
  }

  public static AccountManager getInstance() {
    if (instance == null) {
      instance = new AccountManager();
    }
    return instance;
  }

  public void transfer(Account from, Account to, double amount) {
    // Transfer money from one account to another
  }

  public void withdraw(Account account, double amount) {
    // Withdraw money from an account
  }

  public void changePin(Account account, String newPin) {
    // Change the PIN for an account
  }

  // Other methods for managing Person and Account objects...
}

To use the AccountManager class, other classes would simply need to call the getInstance method to obtain the singleton instance of the class, and then use the instance’s methods to perform operations on the Person and Account objects. For example:

// Create a new Person and Account
// be careful: where will these four Person objects go? 
AccountManager manager = AccountManager.getInstance();
manager.person = new Person("John Doe", "123 Main St.");
manager.person = new Person("Carla Jameson", "323 Main St.");
manager.person = new Person("Rafael Martin", "3 Glorious St.");
manager.person = new Person("Pau Vila", "63 Sesamo St.");

But maybe, this is not the best approach for several reasons …

2.4 Approach #2: all static-methods AccountManager

It may work as follows. In fact, if we want to manage accounts (as a banking-centered problem, not a person-centered one), it could be better that Account has Person:

classDiagram

class Person {
  -name: String
  -surname: String
  -age: int
}

class Account {
  -accountNumber: String
  -pin: int
  -balance: double
  -person: Person
}

Account *-- Person

public class Account {
  private Person person;
  //other fields

  public Account(Person person) {
    this.person = person;
  }

  public Person getPerson() {
    return this.person;
  }

  public void setPerson(Person person) {
    this.person = person;
    
    //constructor, getters, setters and methods
  }
}

So, in this case, we could use a List object to save all the Account objects with the list:

import java.util.List;
import java.util.ArrayList;

public class AccountManager {
  private List<Account> accounts;

  private AccountManager() {
    this.accounts = new ArrayList<>();
  }

  public List<Account> getAccounts() {
    return this.accounts;
  }

  // we should manage how to add/remove accounts to/from accounts list

  public static void deposit(Account account, double amount) {
    // Code to deposit the specified amount to the account
  }

  public static void changePin(Account account, int newPin) {
    // Code to change the PIN of the specified account
  }

  public static void transfer(Account fromAccount, Account toAccount, double amount) {
    // Code to transfer the specified amount from the fromAccount to the toAccount
  }
  
  public static void withdrawal(Account account, double amount) {
    // Code to withdraw the specified amount from the given account
  }
}

2.5 Approach 3: Singleton, any static-method

In this approach:

  • Account has Person
  • AccountManager is Singleton and there is no static-methods anywhere
  • and we create just one object form AccountManager to manage accounts
import java.util.List;
import java.util.ArrayList;

public class AccountManager {
  private static AccountManager manager = new AccountManager();
  private List<Account> accounts;

  // we should manage how to add/remove accounts to/from accounts list

  private AccountManager() {
    this.accounts = new ArrayList<>();
  }

  public static AccountManager getInstance() {
    return manager;
  }

  public List<Account> getAccounts() {
    return this.accounts;
  }

  public  void deposit(Account account, double amount) {
    // Code to deposit the specified amount to the account
  }

  public  void changePin(Account account, int newPin) {
    // Code to change the PIN of the specified account
  }

  public  void transfer(Account fromAccount, Account toAccount, double amount) {
    // Code to transfer the specified amount from the fromAccount to the toAccount
  }
  
  public  void withdrawal(Account account, double amount) {
    // Code to withdraw the specified amount from the given account
  }
}

2.6 Test: AccountManagerTest

AccountManagerTest could be like this:

  • with AccountManager all static-methods no-singleton
  • Account has Person
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class AccountManagerTest {
  @Test
  public void testDeposit() {
    Account account = new Account(new Person("John Doe"));
    double initialBalance = account.getBalance();
    double depositAmount = 100.00;

    AccountManager.deposit(account, depositAmount);
    double finalBalance = account.getBalance();

    assertEquals(initialBalance + depositAmount, finalBalance);
  }

  @Test
  public void testChangePin() {
    Account account = new Account(new Person("John Doe"));
    int initialPin = account.getPin();
    int newPin = 1234;

    AccountManager.changePin(account, newPin);
    int finalPin = account.getPin();

    assertEquals(newPin, finalPin);
  }

  @Test
  public void testTransfer() {
    Account fromAccount = new Account(new Person("John Doe"));
    Account toAccount = new Account(new Person("Jane Doe"));
    double initialFromAccountBalance = fromAccount.getBalance();
    double initialToAccountBalance = toAccount.getBalance();
    double transferAmount = 100.00;

    AccountManager.transfer(fromAccount, toAccount, transferAmount);
    double finalFromAccountBalance = fromAccount.getBalance();
    double finalToAccountBalance = toAccount.getBalance();

    assertEquals(initialFromAccountBalance - transferAmount, finalFromAccountBalance);
    assertEquals(initialToAccountBalance + transferAmount, finalToAccountBalance);
  }
  
  @Test
  public void testWithdrawal() {
    Account fromAccount = new Account(new Person("John Doe"));
    Account toAccount = new Account(new Person("Jane Doe"));
    double initialFromAccountBalance = fromAccount.getBalance();
    double withdrawalAmount = 100.00;

    AccountManager.withdrawal(fromAccount, withdrawalAmount);
    double finalFromAccountBalance = fromAccount.getBalance();

    assertEquals(initialFromAccountBalance - withdrawalAmount, finalFromAccountBalance);
  }
}

3 Step-by-step

  1. Create Maven Project with JUnit
  2. Create Person class
  3. Create Account class
  4. Test Person and Account objects
  5. Write operations (withdrawal, transfer,change pin) as a static methods in AccountManager
  6. Test Person and Account objects and features
  7. Add singleton pattern to AccountManager class
  8. Test AccountManager class