Correct way to construct a Java console program

3.6k views Asked by At

I'm an old-time programmer from the 80s proficient with C and Pascal. I'm pretty new to Java and its concept. In trying to learn the new ways of Java using OOP techniques I'm rather confused about the right way of putting an application together.

I'm trying to build a basic program with the following rules.

An organisation has its own in-house library. The Staff class is general and has Name and Phone. Some of the employees is a librarian. The others are members. So Member and Librarian are specialised classes. I presume we call them inherited or extended in this context.

Now I'm trying to build basic functionality like input and print a member record. I'm trying to build a text-based menu. Perhaps later I'll consider sub-menus. For now it's just a simple menu.

I don't know if I should make the menu an object as well but that's what I think I'll do.

With this in mind, here's what I've done.

My main program:

public class Library extends Menus {
    public static void main(String[] args) {
        Menus Start = new Menus();
        Start.ShowMainMenu();

    }
}

StaffClass.java

public class StaffClass {
    private String Name;
    private String Phone;

    public void InputData() {
        Scanner UserInput = new Scanner(System.in);

        System.out.print("Enter staff name "); Name = UserInput.nextLine();
        System.out.print("Enter staff phone number "); Phone = UserInput.nextLine();

    }

    public void PrintData() {
        System.out.println("Name : " + Name);
        System.out.println("Phone : " + Phone);

    }
}//end StaffClass

Menus.java

public class Menus extends MemberClass {

    int c;
    public void ShowMainMenu() {
        Scanner ui = new Scanner(System.in);
        while(1==1) {
            System.out.println("Main menu");
            System.out.println("1. Add student");
            System.out.println("2. Display all");
            System.out.println("3. exit");
            System.out.print("Enter choice"); c = ui.nextInt();

            switch(c) {
                case 1 : getInputs(); /*System.out.println("option 1");*/ break;
                case 2 : ShowAllInfo(); break;
                case 3 : System.out.println("Leaving the program now..."); System.exit(0); break;
                default  : System.out.println("error.");
            }

        }

    }

}

MemberClass.java

public class MemberClass extends StaffClass {
    int TotalBooks;
    public void getInputs() {
        InputData();
        UpdateTotalBooks();
    }

    public void ShowAllInfo() {
        PrintData();
        System.out.println("total books taken = " + TotalBooks);
    }

    public void UpdateTotalBooks() {
        Scanner ui = new Scanner(System.in);

        System.out.print("Enter number of books "); TotalBooks = ui.nextInt();        

    }

}

It's the first Java program that I've put together with so many classes in it + a menu system.

My key question is if this is the correct way of assembling a program in Java or is there another way.

Please feel free to suggest the best options/changes to the code. But I'd like to keep the concept of generalisation and specialisation.

Thanks!

2

There are 2 answers

7
Erik Kaplun On BEST ANSWER

You are overusing classes — classes are meant to represent data types, or be holders for static methods (which are essentially just functions). You however, are using classes as a means to distribute functions and then apply bogus inheritance. Also, you're inheriting from Menus and instatiating Menus in the same class — that makes no sense whatsoever.

In other words, you don't need to use inheritance and a lot of classes just for the sake of it. There's nothing in Java other than a lot of incompetent developers who overuse classes that forces or recommends overuse of classes and inheritance.

Functions, preferrably ones with no side effects/global state, are much much easier to reason about and get right. Whereas complicated state variables scattered across a parent class chain is a shortcut to a big headache.

I would use something simple such as:

public class Library {
    public static void main(final String[] args) {
        showMainMenu();
    }

    int totalBooks;

    public static void showMainMenu() {
        final Scanner ui = new Scanner(System.in);
        while (true) {
            System.out.println("Main menu");
            System.out.println("1. Add student");
            System.out.println("2. Display all");
            System.out.println("3. exit");
            System.out.print("Enter choice");

            switch (ui.nextInt()) {
                case 1:
                    getInputs();
                    /*System.out.println("option 1");*/
                    break;
                case 2:
                    showAllInfo();
                    break;
                case 3:
                    System.out.println("Leaving the program now...");
                    System.exit(0);
                    break;
                default:
                    System.out.println("error.");
            }
        }
    }

    public static void getInputs() {
        inputData();
        updateTotalBooks();
    }

    public static void showAllInfo() {
        printData();
        System.out.println("total books taken = " + totalBooks);
    }

    public static void updateTotalBooks() {
        System.out.print("Enter number of books ");
        totalBooks = new Scanner(System.in).nextInt();
    }
}

If you need to split this up in different modules, do NOT use inheritance. Instead, create something like a State or Database class, and pass that around between static functions that either modify the passed in data or, if you want to peek into the world of functional programming and immutable data structures, return an altered copy of the data:

class State {
  // make these non-final if you want State to be mutable
  // (I'm too lazy to generate getters; and in Scala you don't have to)
  public final int totalBooks = 0;
  public final String someOtherState = "hello";

  public State(final int totalBooks, final String someOtherState) {
    this.totalBooks = totalBooks;
    this.someOtherState = someOtherState;
  }

  public State withTotalBooks(final int newValue) {
    return new State(newValue, someOtherState);
  }
  public State withSomeOtherState(final int newValue) {
    return new State(totalBooks, newValue);
  }
}

class App {
  public static void main(final String[] args) {
    State state = new State();
    // at the top-most level of the program, it's OK to use global state
    state = BookManager.updateNumBooks(state)
  }
}

class BookManager {
  public static State updateNumBooks(final State state) {
    return state.withTotalNumBooks(123);
  }

  // or, if State is mutable:
  public static void updateNumBooks(final State state) {
    // UI code here
    state.totalNumBooks = 123;
  }      
}

class HelloModule {
  public static State updateMsg(final State state) {
    // UI code
    return state.withSomeOtherState("hello world");
  }

  // or, if State is mutable:
  public static void updateMsg(final State state) {
    // UI code
    state.someOtherState = "hello world";
  }
}

// etc

And, as a final remark: all of this would look much nicer in a concise functional programming JVM language such as Scala (or even Frege). If you must stay on Java, at least read something up about Functional Programming and how you could benefit from it in the way you design your programs:

6
Dave Newton On

Too long for a comment, but not an answer.

  1. There will be classes. Hopefully many of them.
  2. Java naming conventions should be followed, e.g., name methods starting with a lowercase letter.
  3. StaffClass is a poor name, instead just use Staff.
  4. Consider providing input to it in a different way: as is, Staff can only be populated by its own input methods. But how data gets into a Staff instance should be completely unrelated to what a Staff actually is.
  5. There is no "One Right Way" to assemble a Java program from its classes, or even One Right Way to break up responsibilities. But the Single Responsibility Principle is important. Equally important is the notion that methods (and possibly classes) should always operate at the same level of abstraction.