3
\$\begingroup\$

I am a beginner who just picked up Java (2 months ago) and I am solving some questions. For this question, the overall problem wants me to choose the cheapest price plan, so I listed out the possibilities of every plan and 'hardcoded' the logic by inputting the number of services, as the services is more important than the plan. However, I would like to improve the algorithm of this method and as a beginner my foundation is very weak.

Is there any other ways where I can further improve this method or is there any other solutions? This is because if I need to implement a new service, my entire solution will be flawed. Hence, I am looking at constructors/interfaces and arrays. However, it gets very confusing when there is a plan and services being involved and then to get the cheapest price as well.

Therefore, I would appreciate any help or advise to improve on this method of solving. Please pardon me for my weak explanation and lack of concept in Java as this is my first post.

Problem statement

Create a java program

The seller from corporation Test provides the following plans and its price:

  • PLAN1: (voice, email), $100 per year
  • PLAN2: (email, database, admin), $150 per year
  • PLAN3: (voice, admin), $125 per year
  • PLAN4: (database, admin), $135 per year

The customer wants (email, voice, admin) service. He has the following choices to cover his need:

  • (PLAN1, PLAN2): 100 + 150 = $250 (PLAN1, PLAN3): 100 + 125 = $225
  • (PLAN2, PLAN3): 150 + 125 = $270 (PLAN1, PLAN4): 100 + 135 = $235
  • (PLAN2, PLAN3, PLAN4) = 150 + 125 + 135 = $410
  • …// other possible plan choices omitted

And the minimum price is $225, PLAN1 + PLAN3.

My solution

package practice;

import java.util.Scanner;

public class Test {

    private static Scanner input;

    public static void main(String[] args) {

        input = new Scanner(System.in);
        System.out.println("We have the following services:");
        System.out.println("Voice, Database, Admin, Email");
        System.out.println("How many services would you like?");

        // input number of services
        int choice = input.nextInt();

        // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        // Logic starts here
        // when number of services required is 1
        switch (choice) {
        case 1:
            Scanner input1 = new Scanner(System.in);
            System.out.println("What type of services would you like?");
            System.out.println("1. Voice");
            System.out.println("2. Database");
            System.out.println("3. Admin");
            System.out.println("4. Email");

            // input 1 option
            int choice1 = input1.nextInt();

            switch (choice1) {
            case 1:
                System.out.println("$100");
                break;
            case 2:
                System.out.println("$135");
                break;
            case 3:
                System.out.println("$125");
                break;
            case 4:
                System.out.println("$100");
                break;

            default:
                System.out.println("Invalid option. Please try again");

            }
            break;

        // when number of services required is 2
        case 2:
            Scanner input2 = new Scanner(System.in);
            System.out.println("What type of services would you like?");
            System.out.println("1. Voice");
            System.out.println("2. Database");
            System.out.println("3. Admin");
            System.out.println("4. Email");

            // input 2 options
            int choice2 = input2.nextInt();
            int choice3 = input2.nextInt();

            // plan(1,4) -> $100
            if (choice2 == 1 && choice3 == 4) {
                System.out.println("$100");

                // plan(1,3) -> $125
            } else if (choice2 == 1 && choice3 == 3) {
                System.out.println("$125");

                // plan(2,3) & (3,4) -> $125
            } else if ((choice2 == 2 && choice3 == 3) || (choice2 == 3 && choice3 == 4)) {
                System.out.println("$150");

            } else
                System.out.println("Invalid option. Please try again");

            break;

        // when number of services required is 3
        case 3:
            Scanner input3 = new Scanner(System.in);
            System.out.println("What type of services would you like?");
            System.out.println("1. Voice");
            System.out.println("2. Database");
            System.out.println("3. Admin");
            System.out.println("4. Email");

            // input 3 options
            int choice4 = input3.nextInt();
            int choice5 = input3.nextInt();
            int choice6 = input3.nextInt();

            // plan(1,3,4) -> $225
            if (choice4 == 1 && choice5 == 3 && choice6 == 4) {
                System.out.println("$225");

                // plan(1,2,3) -> $235
            } else if (choice4 == 1 && choice5 == 2 && choice6 == 3) {
                System.out.println("$325");

                // plan(2,3,4) -> $150
            } else if (choice4 == 2 && choice5 == 3 && choice6 == 4) {
                System.out.println("$150");

            } else
                System.out.println("Invalid option. Please try again");

            break;

        // when number of services required is all 4
        case 4:
            System.out.println("All 4 services will cost $250");
            break;

        default:
            System.out.println("Invalid input.");

        }
    }
}

Output

We have the following services:

Voice, Database, Admin, Email

How many services would you like? 

3 

What type of services would you like?

1. Voice

2. Database

3. Admin

4. Email 

1 

3 

4 

$225
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Thanks for sharing your code.

your solution is straight procedural. There is nothing wrong with procedural solutions as such but Java is an object oriented programming language and therefore you should learn to find OO-ish solutions to problems.
However: this is just an advice, no judgement.

Here is what I consider problematic in your code:

General approach

no calculation

Your code hides the concept of the Service Plan from the problem statement by cascading decisions. This leads to the "spaghetti code" we see and what you consider problematic yourself.

On top of that your code fails if the user enters the service IDs in an unexpected order, eg.: 4 3 1.

A proper solution would try to calculate the best plan combination, so that adding new plans and/or services would just be a configuration.

no reuse of variables

At every assignment you create a new variable.

You declare multiple variables input with numbers. There should be only one variable input since System.in is a singelton and cannot be accessed concurrently anyway. On top of that you declared input (without number) as a class variable. Since you only use this variable at only one method in our code which is the same where you assign it its value this is not needed.

The variables choice1, choice2 and choice4 hold the same value regarding the business case: the first selected service ID. There should be only one variable firstSelectedServiceID to be used in all cases of the outer switch. Similar is true for the other numbered choice variables.

Naming

Finding good names is the hardest part in programming. So always take your time to think carefully of your identifier names.

Choose your names from the problem domain

You have some identifiers which are named after their technical implementation: the same name with numbers. This makes your code hard to read and hard to understand. Your variables should better be named like this:

  • choice: should be numberOfServices
    instead of writing a comment you can name the variable accordingly
  • choice1, choice2 and choice4 as mentioned before
  • choice3 and choice5 should be secondSelectedServiceID
  • choice6 should be thirdSelectedServiceID
\$\endgroup\$
1
  • \$\begingroup\$ Hi Timothy, thanks for taking the time out to comment on my codes. I am deeply humble by the advice and will looked to revise on my foundations level. It is my first time learning programming on my own and java is my first language. I really appreciate your advice and will looked to implement them in the near future. Cheers! :) \$\endgroup\$
    – Alan Chu
    Nov 20, 2019 at 18:56
2
\$\begingroup\$

I'm going to code review from the top down. Don't be discouraged, but I have a lot of suggestions:

Change name of class 'Test'

Lots of projects put classes outside of the production build based on the ClassName starting or ending with Test. Of course it doesn't matter when you're just messing around, but it's good practice to avoid such a name for a class that is not an Automated test.

Another problem arises when you create a test for Test, if you follow the common naming pattern you're left with TestTest, which is an awkward name.

Change name of 'input'. Set it as Final.

input is a poor name. Normally input would refer to input from somewhere, such as the user. Not the object used to get the input. I suggest renaming it scanner. You can also declare it as final since it never needs to change.

Use methods & don't repeat yourself.

Whenever you copy & paste some code in more than 1 place, you should ask yourself if it could be a method. This holds true even when you make a slight change to the pasted code.

I work with a fairly large code base and I've only seen a maximum of 1-2 lines of code repeated. Even that could be reduced to 0. In other words, you should never repeat yourself.

Even if you forget about re-usability, You should get into the habit of creating methods wherever they make sense to help readability of your code.

This could be a method:

Scanner input1 = new Scanner(System.in);
System.out.println("What type of services would you like?");
System.out.println("1. Voice");
System.out.println("2. Database");
System.out.println("3. Admin");
System.out.println("4. Email");

int choice1 = input1.nextInt();

But we already have an input, so we don't need to instantiate another Scanner object. You can either take it as an parameter, or just reference it directly if it's kept as a class variable.

So a better method would look like:

private static int promptUserForBeginningServices(Scanner scanner)
{
    System.out.println("We have the following services:");
    System.out.println("Voice, Database, Admin, Email");
    System.out.println("How many services would you like?");

    return scanner.nextInt();
}

..However you may notice this method is doing 2 things: Prompting the user, & getting a result for number of services. Methods should really only be doing 1 thing. But this is our first iteration of refactoring. So we will leave it for now.

Change the names of 'choice'

A better name would be choiceEnteredByUser, or typeOfServiceSelected.

"choice" is ambiguous. Actually, it's meaningless. You could have named it x & it would be as descriptive.

Commenting

Commenting where the logic start is not helpful. Use comments to describe what code does, when it's not clear. Actually, your first approach is to use descriptive methods & descriptive method names. If it's still not 100% clear, use a comment.

ENUMS

Whenever You have a switch statement, you should ask yourself if you should be using an ENUM. That's not to say every switch statement should be an enum, but a lot of them should be. This is one of those times.

"1" is meaningless. You should never have meaningless code.

...That being said, you don't need a switch statement at all, you should be using a Map or parallel arrays.

Magic Numbers

Magic numbers / magic strings are those literal values you have hanging around. The numbers in your switch statement, the strings you output to the user ("What type of services would you like?") these should be declare as final static variables at the top of your class. This will help maintenance & readability.

End notes

I normally post the finished product when doing code reviews, but I think in this case it would do more damage than good. Don't try to move from 0 - 10 in one round of refactoring. You'll learn a LOT from simply adding methods. Your code will become 10x easier to read, you'll be able to see all the useless duplication & be able to make changes a lot easier.

After you've added methods, adding an ENUM will be another 'aha moment' or epiphany (for lack of better term).

Once you have an ENUM & methods, see if you can remove all of your magic strings.

Once you have all your String messages at the top, you'll probably notice you can group them... See if you can put them into Arrays. Afterwards, your code has literally been cut in half (by line number, assuming you can print those array messages in a loop).

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Hi sir, I have not tried using ENUMS but I will look to implement it in the near future. Thanks for the advice, really appreciate you taking the time out to help me out. :) \$\endgroup\$
    – Alan Chu
    Nov 20, 2019 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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