2

In the case of an application with a single simple responsibility (eg, a simple replacement for grep or wc), which of these designs is preferred and why? I find that they are all testable and they all do their job. Does any of them have any significant advantages over the other?

1: All methods in the main class:

public class App {
    public static void main(String[] args)
    {
        App app = new App();
        AppInput input = app.readInput();
        AppOutput output = app.processInputToOutput(input);
        app.writeOutput(output);
    }

    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }
}

2: Basic methods in a separate class, main class only making calls to read and process input, and output result:

public class App {
    public static void main(String[] args)
    {
        AppWorker worker = new AppWorker();
        AppInput input = worker.readInput();
        AppOutput output = worker.processInputToOutput(input);
        worker.writeOutput(output);
    }
}

public class AppWorker {
    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }
}

3: Have all the work in a separate worker class, with the main class only instantiating the worker and triggering it to do its work:

public class App {
    public static void main(String[] args)
    {
        AppWorker worker = new AppWorker();
        worker.doYourWork();
    }
}

public class AppWorker {
    public void doYourWork()
    {
        AppInput input = readInput();
        AppOutput output = processInputToOutput(input);
        writeOutput(output);
    }

    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }

}
3
  • 2
    Please don't leave it up to us to guess what you've changed. Explain for each block of code what you did and why you think it is an appropriate solution. Also make sure your examples compile (at 2 app.read/process/write should be worker.read/process/write, at 3 it should be this.).
    – CodeCaster
    Nov 26, 2013 at 9:45
  • @CodeCaster: Nice catch, thanks and sorry for the bad post!
    – stackular
    Nov 26, 2013 at 9:57
  • Consider code reuse here. You can't pull main() out and use it externally.
    – Phoshi
    Nov 26, 2013 at 10:10

3 Answers 3

5

Please notice that for the most part, these are nitpick details and refer to the general best practice of structuring code, however I'd be arrogant to assume to know how you should write your program.

That being said, lets look at #1:

Solution #1

// Name of class containing main should usually be called Main and 
// Main in of itself is a single responsibility class, meaning it does nothing
// other than launch your program
public class App {
    public static void main(String[] args)
    {
        // Here, "App" should be a more significant name relevant to what it does.
        // Perhaps "Pipe" would be better suited?
        App app = new App();
        AppInput input = app.readInput();
        AppOutput output = app.processInputToOutput(input);
        app.writeOutput(output);
    }

    // These methods would obviously belong to Pipe and not to App/Main
    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }
}

Solution #2

// Same discussion as above with name of class containing Main
public class App {
    public static void main(String[] args)
    {
        // Correcting minor error using "app" in the place of "worker"
        AppWorker worker = new AppWorker();
        AppInput input = worker.readInput();
        AppOutput output = worker.processInputToOutput(input);
        worker.writeOutput(output);
    }
}

// AppWorker seems to uphold the suggestions mentioned above, but the name is a problem.
// "Worker" makes me think of threads.  If it isn't a thread, you would probably be better
// off calling it with a name appropriate for its job (again, I propose Pipe as
// it takes input and writes output).  
public class AppWorker {
    // "readInput" and "writeOutput" are redundant.  Better left as "read/write"
    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }
}

Solution #3

// Same discussion as above with name of class containing Main
public class App {
    public static void main(String[] args)
    {
        // This is simply moving the job of Main to another class.  
        // In more complicated programs, this may even be smart.  Controller classes 
        // are usually used when Main begins to be cluttered, leaving the logic of
        // argument handling in Main and putting the rest in the controller class.
        // However in this case, "AppWorker" does nothing more than the work of App/Main
        // which makes AppWorker simply redundant.
        AppWorker worker = new AppWorker();
        worker.doYourWork();
    }
}

public class AppWorker {
    public void doYourWork()
    {
        // You mean to use this here I assume?
        // App app = new App();
        AppInput input = this.readInput();
        AppOutput output = this.processInputToOutput(input);
        this.writeOutput(output);
    }

    // If AppWorker is controller as it seems to be, the following methods need to be
    // protected or private as this class controls and is not controlled.  Only 
    // public methods aside from "execute" method should be to set optional parameters.
    public AppInput readInput() { .. }
    public AppOutput processInputToOutput(AppInput input) { .. }
    public void writeOutput(AppOutput output) { .. }
}

My Solution

If I may be so bold, applying the points above, I would write it as the following:

public class Main{
    public static void main(String[] args)
    {
        Pipe pipe = new Pipe();
        PipeInput input = pipe.read();
        PipeOutput output = pipe.processInputToOutput(input);
        pipe.write(output);
    }
}

public class Pipe {
    public PipeInput read() { .. }
    public PipeOutput processInputToOutput(PipeInput input) { .. }
    public void write(PipeOutput output) { .. }
}

Notice that the logic hasn't been removed from Main, but it is still left simple. If I saw that my Main were getting cluttered, I would consider creating a class to handle some of the more trivial details, but not to the point where I would create a class that does everything Main does. Not even when I want to create a controller class do I port all the logic. Controller can make some assumptions about the input, and Main can provide the necessary tests.

The work of App is now called Pipe. Notice that it follows the single responsibility principle. If I wanted to add more logic, I would be careful not to automatically add it to Pipe, since Pipe already does everything it should and nothing more. And finally, note that the names of the methods previously called readInput and writeOutput are now simply read and write, since in the context of Pipe, it is clear what is intended by the parameters passed.

Again, these are nitpicky details. I'm simply following the best practices that I've learned over the years. I hope that you can also benefit from these tips!

2

I recommend doing the all-in-one, then refactor into separate classes as the application grows. There's no need to add complexity into the design until it's needed. How you split it later depends on how your application is growing:

  • Different types of input. Maybe you're reading from a String or a network stream instead of a file. In this case, you want to create classes and/or interfaces on the input and instantiate them from main.
  • Different types of output. Maybe you're outputting to a database or a web page instead of a terminal. In this case, you want to create classes and/or interfaces on the output and instantiate them from main.
  • Different kinds of processing. In this case, you want classes and/or interfaces for the processing step and instantiate those from main.
  • Different kinds of main tasks. Maybe you're adding a GUI option. In this case, your separate AppWorker idea to do the entire thing is a good refactor, because you can make an AppWorkerCli and an AppWorkerGUI.

Some people try to avoid any rearchitecting by guessing how the application will grow, but in my experience, developers are poor guessers.

1
  • "...developers are poor guessers" That is too true.
    – Neil
    Nov 27, 2013 at 13:13
1

I like all three ways of doing it, and the length of code would tell me how it should be organized. I don't mind two or three pages of code in my Application object. Once it gets to be more than that, I like to see more effort put into separating responsibilities of classes.

The application should either contain the entire code, or only be a boot-strapping to allow the application to run. That is, it should create your controller and views, and then not be involved in the main body of the application. Shutdown or exceptional errors would be nice to contain within the Application class.

In the case where the application is a short-term worker, it does come down to code-size for me.

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