3
\$\begingroup\$

So I wanted to write a basic console based banking app to practice my skills. It makes use of BigDecimal to ensure accuracy. Here is the MVP I plan to enhance it with all banking features soon. I tried to follow the MVC idea as close as possible, I'm pretty happy with the outcome, although I was questioning myself about some things like how I was creating the new account. As of now it can only accept one user, I plan to add some sort of data persistence like SQLite to practice with. Also there is no check to enforce the user to enter their details when creating an account so those fields can be blank. I will be enhancing this in the near future. Any peer reviews greatly appreciated on how my style could be improved, better way of handling how I did it or anything etc.

Main.java

public class Main
{
    public static void main(String[] args)
    {
        Prompter prompter = new Prompter();

        prompter.greetUser();
        do
        {
            prompter.prompt();
            prompter.processPrompt();
        }   while(!prompter.isFinishedBanking);
    }
}

Prompter.java

import java.util.Scanner;

public class Prompter
{
    protected boolean isFinishedBanking;
    protected boolean areCustomerDetailsCorrect;
    private String option;

    private Scanner scanner = new Scanner(System.in);
    private Account account = new Account();

    public void prompt()
    {
        isFinishedBanking = false;
        System.out.print("> ");
        option = scanner.nextLine();
    }

    public void processPrompt()
    {
        if (option.toLowerCase().equals("exit"))
        {
            System.out.println("\nThank you for banking with JBank, goodbye!");
            isFinishedBanking = true;
        }

        else if (option.toLowerCase().equals("home"))
        {
            System.out.println();
            greetUser();
        }

        else if (option.toLowerCase().equals("options"))
        {
            showOptions();
        }

        else if (option.toLowerCase().equals("open"))
        {
            if(account.isUserCustomer)
            {
                System.out.println("Error! you have already opened an account.");
            }

            else
            {
                do 
                {
                    openAccountPrompt();
                    System.out.println("\nAre these details correct? Y/n");
                    printCustomerDetails();
                    prompt();

                    if (option.toLowerCase().equals("n"))
                    {
                        areCustomerDetailsCorrect = false;
                    }

                    else
                    {
                        areCustomerDetailsCorrect = true;
                        account.openAccount();
                        System.out.println("\nCongratulations! You have successfully opened a new account.");
                    }
                } while(!areCustomerDetailsCorrect);
            }
        }

        else if (option.toLowerCase().equals("deposit"))
        {
            if (!account.isUserCustomer)
            {
                System.out.println("\nError! you must open an account before you can use this option.\n");
            }

            else
            {
                System.out.println("\nEnter amount you with to deposit.");
                prompt();
                account.depositFunds(option);
                System.out.println("\nSuccess! your new balance is: " + account.getAccountBalance());
            }
        }

        else if (option.toLowerCase().equals("check"))
        {
            if (!account.isUserCustomer)
            {
                System.out.println("\nError! you must open an account before you can use this option.\n");
            }
            System.out.println();
            System.out.println("Your account balance is: " + account.getAccountBalance());
        }

        else if (option.toLowerCase().equals("withdraw"))
        {
            if (!account.isUserCustomer)
            {
                System.out.println("\nError! you must open an account before you can use this option.\n");
            }

            else
            {
                System.out.println("\nEnter amount you with to withdraw.");
                prompt();

                if (account.checkAccountForAvailableFunds(option))
                {
                    System.out.println("\nError! you don't have the available funds in your account to complete this transaction. Your available balance is: " + account.getAccountBalance());
                }
                else
                {
                    account.withdrawFunds(option);
                    System.out.println("\nSuccess! your new balance is: " + account.getAccountBalance());
                }
            }
        }

        else
        {
            System.out.println("\nError! I didn't recognize your response, please try again.\n");
        }
    }

    public void openAccountPrompt()
    {
        System.out.println("\n\nEnter your first name.");
        prompt();
        account.setCustomerFirstName(option);

        System.out.println("\nEnter your last name.");
        prompt();
        account.setCustomerLastName(option);

        System.out.println("\nEnter your address.");
        prompt();
        account.setCustomerAddress(option);

        System.out.println("\nEnter your phone number.");
        prompt();
        account.setCustomerPhoneNumber(option);

        System.out.println("\nEnter your email address.");
        prompt();
        account.setCustomerEmailAddress(option);

        System.out.println("\nEnter amount to fund your new account.");
        prompt();

        // If left blank defaults to zero.
        if (option.equals(""))
        {
            option = "0";
        }
        account.setAccountBalance(option);
    }

    public void printCustomerDetails()
    {
        System.out.printf("Name: %s %s\nAddress: %s\nTelephone number: %s\nEmail address: %s\nBeginning balance: %s\n\n",
                          account.getCustomerFirstName(), account.getCustomerLastName(),
                          account.getCustomerAddress(),
                          account.getCustomerPhoneNumber(),
                          account.getCustomerEmailAddress(),
                          account.getAccountBalance());
    }

    public void greetUser()
    {
        System.out.println("Welcome to JBank! Type \"options\" for a list of options, \"home\" to get back here, or \"exit\" to exit.\n");
    }

    public void showOptions()
    {
        System.out.println("\nOptions: \"open\" an account, \"deposit\" funds, \"check\" balance, or make a \"withdraw\"\n");
    }
}

Account.java

import java.math.BigDecimal;

public class Account
{
    public boolean isUserCustomer = false;
    private Account customerAccount;
    private String customerFirstName;
    private String customerLastName;
    private String customerAddress;
    private String customerPhoneNumber;
    private String customerEmailAddress;
    private String stringAccountBalance;
    private BigDecimal bigDecimalAccountBalance = BigDecimal.ZERO;

    public Account()
    {

    }

    public Account(String customerFirstName, String customerLastName, String customerAddress, String customerPhoneNumber, String customerEmailAddress, String stringAccountBalance)
    {
        this.customerFirstName = customerFirstName;
        this.customerLastName = customerLastName;
        this.customerAddress = customerAddress;
        this.customerPhoneNumber = customerPhoneNumber;
        this.customerEmailAddress = customerEmailAddress;
        this.stringAccountBalance = stringAccountBalance;
    }

    public String getCustomerFirstName()
    {
        return customerFirstName;
    }

    public void setCustomerFirstName(String customerFirstName)
    {
        this.customerFirstName = customerFirstName;
    }

    public String getCustomerLastName()
    {
        return customerLastName;
    }

    public void setCustomerLastName(String customerLastName)
    {
        this.customerLastName = customerLastName;
    }

    public String getCustomerAddress()
    {
        return customerAddress;
    }

    public void setCustomerAddress(String customerAddress)
    {
        this.customerAddress = customerAddress;
    }

    public String getCustomerPhoneNumber()
    {
        return customerPhoneNumber;
    }

    public void setCustomerPhoneNumber(String customerPhoneNumber)
    {
        this.customerPhoneNumber = customerPhoneNumber;
    }

    public String getCustomerEmailAddress()
    {
        return customerEmailAddress;
    }

    public void setCustomerEmailAddress(String customerEmailAddress)
    {
        this.customerEmailAddress = customerEmailAddress;
    }

    public String getAccountBalance()
    {
        return bigDecimalAccountBalance.toString();
    }

    public void setAccountBalance(String stringAccountBalance)
    {
        this.stringAccountBalance = stringAccountBalance;
        bigDecimalAccountBalance = new BigDecimal(stringAccountBalance);
    }

    public void openAccount()
    {
        customerAccount = new Account(customerFirstName, customerLastName, customerAddress, customerPhoneNumber, customerEmailAddress, stringAccountBalance);
        isUserCustomer = true;
    }

    public void depositFunds(String stringDepositAmount)
    {
        BigDecimal bigDecimalDepositAmount = new BigDecimal(stringDepositAmount);
        bigDecimalAccountBalance = bigDecimalAccountBalance.add(bigDecimalDepositAmount);
    }

    public void withdrawFunds(String stringWithdrawAmount)
    {
        BigDecimal bigDecimalWithdrawAmount = new BigDecimal(stringWithdrawAmount);
        bigDecimalAccountBalance = bigDecimalAccountBalance.subtract(bigDecimalWithdrawAmount);
    }

    public boolean checkAccountForAvailableFunds(String stringWithdrawAmount)
    {
        boolean isAvailable;
        BigDecimal bigDecimalWithdrawAmount = new BigDecimal(stringWithdrawAmount);

        // Checks to make sure there are enough funds in the account to make the withdraw.
        if (bigDecimalAccountBalance.subtract(bigDecimalWithdrawAmount).compareTo(BigDecimal.ZERO) < 0)
        {
            isAvailable = true;
        }
        else
        {
            isAvailable = false;
        }
        return isAvailable;
    }
}
\$\endgroup\$
3
\$\begingroup\$

Your Prompter class is quite big, and has several responsibilities: prompting for action, validating input and performing the action. It is very volatile, as any new action requires also updating the prompter. I'd break out the actions to their own classes, implementing an BankingAction interface. The prompter would then contain a list of actions, either through a constructor parameter or registered via a registerAction(BankingAction action) method. When prompting, it'd check this list, dispatch the action to the correct instance and be done with it.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

The Prompter has a low cohesion.

Cohesion refers to the degree to which the elements of a module/class belong together, it is suggested that the related code should be close to each other, so we should strive for high cohesion and bind all related code together as close as possible. It has to do with the elements within the module/class.

When designing a module we want to achieve a high cohesion.

[...] High Cohesion (or the cohesion's best type - the functional cohesion) is when parts of a module are grouped because they all contribute to a single well-defined task of the module.

When we look at the methods the Prompter provide we can see, that the most of them are used only internally at exactly one place. For example:

public void processPrompt() {
  /* ... */
  else if (option.toLowerCase().equals("options")) {
      showOptions();
  }
  /* ... */
}

public void showOptions() {
    System.out.println("\nOptions: \"open\" an account, \"deposit\" funds, \"check\" balance, or make a \"withdraw\"\n");
}

The showOptions only gets called because the user input fulfills option.toLowerCase().equals("options").

So we can clearly see, that the prompter is divided into different logical groups based on option. Currently the Prompter is more like a broker which additionally prompts too.

One solution to achieve a higher cohesion would be to decouple the broker-part from the promter part by creating for each option a own Prompter-Class, something like:

public void processPrompt() {
    if (option.toLowerCase().equals("exit")){
        exitPrompt.prompt();
        isFinishedBanking = true;
    }

    else if (option.toLowerCase().equals("home")) {
        greetPropter.prompt();
    }

    else if (option.toLowerCase().equals("options")) {
        optionsPrompter.prompt();
    }

    /* .. */
}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.