4
\$\begingroup\$

This is a simple ToDoApp with a CLI that is made useful by adding persistence. A shout out to @Phrancis and @syb0rg for guiding this little project to where it is now.

The structure is rather simple. The basic class is ToDoItem. It is stored in an implementation of the interface ToDoRepository - in InMemoryToDoRepository or ToDoRepositoryWithJSON. Finally, there's the ToDoApp which performs all the command-line processing.

For those who want to test the app, here is a pre-built JAR file (version 0.3). The code is as follows:

ToDoItem.java

package ml.cristatus.todo.model;

/**
 * This is the basic unit of information in this system. Everything is built
 * around this class. This class defines a basic task that has a unique ID
 * that is assigned by a repository. The "name" or content of a task can be
 * updated freely. But the ID is updated only once by the repository. Every
 * task also has a <code>completed</code> boolean flag to indicate its state
 * of completion.
 *
 * @author Subhomoy Haldar
 * @version 0.3
 */
public class ToDoItem implements Comparable<ToDoItem> {

    /**
     * The unique and immutable ID.
     */
    private final long id;
    /**
     * The modifiable content of the item.
     */
    private String name;
    /**
     * The status of the task/item.
     */
    private boolean completed;

    /**
     * This constructor creates a new ToDoItem with ta unique ID and text.
     *
     * @param name The name of the task, or its content.
     * @param id   The unique ID of the item.
     */
    public ToDoItem(String name, final long id) {
        this.name = name;
        this.id = id;
    }

    /**
     * Compares two tasks based on their order of creation. A ToDoItem
     * created later will have a higher ID.
     *
     * @param item The ToDoItem to compare this item with.
     * @return -1, 0, or 1 if this item is created earlier, at the same time as
     * the argument, or created after the argument, respectively.
     */
    @Override
    public int compareTo(ToDoItem item) {
        return Long.compare(id, item.id);
    }

    /**
     * Returns the ID of the ToDoItem.
     *
     * @return The ID of the ToDoItem.
     */
    public Long getId() {
        return id;
    }

    /**
     * Returns the name of the task.
     *
     * @return The name of the task.
     */
    public String getName() {
        return name;
    }

    /**
     * Updates the contents of this task.
     *
     * @param name The new name of the task.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Returns the current status of this item. If the task is completed, it
     * returns <code>true</code> and <code>false</code> otherwise.
     *
     * @return The current status of this item.
     */
    public boolean isCompleted() {
        return completed;
    }

    /**
     * Set the completion status of this task.
     *
     * @param completed The new status to be set.
     */
    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    /**
     * Check if the argument is exactly equal to this item.
     *
     * @param other The argument to compare with.
     * @return <code>true</code> if the objects are equal, <code>false</code>
     * otherwise.
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof ToDoItem)) return false;
        ToDoItem item = (ToDoItem) other;
        return id == item.id && name.equals(item.name);
    }

    /**
     * Returns a handy String representation of the item.
     *
     * @return A handy String representation of the item.
     */
    @Override
    public String toString() {
        return " - " + name + " [ID: " + id + " Completed: " + completed + "]";
    }
}

ToDoRepository.java

package ml.cristatus.todo.repository;

import ml.cristatus.todo.model.ToDoItem;

import java.util.List;

/**
 * The interface that sets down the common functionality that every sort of
 * Item Repository must have. The {@link #save() save()} functionality is a
 * useful and sought after feature, but some Repositories may choose not to
 * implement it.
 *
 * @author Subhomoy Haldar
 * @version 0.3
 */
public interface ToDoRepository {
    /**
     * Returns the List of all {@link ToDoItem}s sorted in the order that
     * they were added.
     *
     * @return A chronological list of ToDoItems.
     */
    List<ToDoItem> findAll();

    /**
     * Returns the ToDoItem that has the required ID, or <code>null</code> if
     * no such ToDoItems exists.
     *
     * @param id The unique ID.
     * @return The ToDoItem that has the required ID.
     */
    ToDoItem findById(Long id);

    /**
     * Inserts a new ToDoItem into the repository that has the content given
     * in the argument and returns the ID assigned to it.
     *
     * @param text The content for the new ToDoItem.
     * @return The ID of the Item inserted.
     */
    Long insert(String text);

    /**
     * Updates the ToDoItem currently in the repository with the given key to
     * contain the new text. If no such Item exists with the same key,
     * nothing is done.
     *
     * @param toDoItem The new, updated item to be registered.
     */
    void update(ToDoItem toDoItem);

    /**
     * Deletes the given Item from the repository, if it exists.
     *
     * @param toDoItem The Item to delete.
     */
    void delete(ToDoItem toDoItem);

    /**
     * Saves the current state of the repository to any form of permanent
     * storage. But this behaviour is not compulsory. An implementation may
     * or may not have any saving mechanism.
     */
    void save();
}

InMemoryToDoRepository.java

package ml.cristatus.todo.repository;

import ml.cristatus.todo.model.ToDoItem;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * This is an implementation of ToDoRepository that stores the items in
 * memory. This does not have a save mechanism. It must by backed by another
 * system to provide support for persistence.
 *
 * @author Subhomoy Haldar
 * @version 0.3
 */
@SuppressWarnings("WeakerAccess")
public class InMemoryToDoRepository implements ToDoRepository {

    private AtomicLong currentId;
    private ConcurrentMap<Long, ToDoItem> toDos;

    /**
     * Creates a new, empty repository with currentID at 0.
     */
    public InMemoryToDoRepository() {
        currentId = new AtomicLong();
        toDos = new ConcurrentHashMap<>();
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public List<ToDoItem> findAll() {
        List<ToDoItem> toDoItems = new ArrayList<>(toDos.values());
        Collections.sort(toDoItems);
        return toDoItems;
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public ToDoItem findById(Long id) {
        return toDos.get(id);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public Long insert(String text) {
        Long id = currentId.incrementAndGet();
        toDos.putIfAbsent(id, new ToDoItem(text, id));
        return id;
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public void update(ToDoItem toDoItem) {
        if (toDos.get(toDoItem.getId()) != null) {
            toDos.replace(toDoItem.getId(), toDoItem);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public void delete(ToDoItem toDoItem) {
        toDos.remove(toDoItem.getId());
        if (toDos.isEmpty()) {
            reset();
        }
    }

    /**
     * This class does NOT provide a saving mechanism.
     */
    @Override
    public void save() {
        // no save mechanism here
    }

    /**
     * Resets the currentID to 1 when the list is empty.
     */
    private void reset() {
        currentId = new AtomicLong();
    }
}

ToDoRepositoryWithJSON.java

package ml.cristatus.todo.repository;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ml.cristatus.todo.model.ToDoItem;

import java.io.*;
import java.util.List;
import java.util.Scanner;

/**
 * This implementation provides a save mechanism. It looks for a
 * <code>toDoData.json</code> file in the <code>Documents</code> folder (or
 * creates one if it can't find one). Then it saves the data in JSON format.
 * <p>
 * It uses an instance of {@link InMemoryToDoRepository} internally inorder
 * to reuse existing functionality.
 *
 * @author Subhomoy Haldar
 * @version 0.3
 */
public class ToDoRepositoryWithJSON implements ToDoRepository {

    /**
     * This is the default save location, which is expected to be same
     * throughout all platforms (hopefully).
     */
    private static final File DEFAULT_FILE = new File(
            System.getProperty("user.home") + "/Documents/toDoData.json"
    );
    /**
     * The Gson instance used every time to provide conversion functionality
     * between JSON and Java representation.
     */
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
    /**
     * If a terminal session extends for too long and some problem occurs at
     * the end, then a lot of data might be lost. Therefore, this class
     * auto saves after a regular period (number of operations).
     */
    private static final int AUTO_SAVE_COUNT = 15;

    /**
     * This class basically wraps around this repository.
     */
    private InMemoryToDoRepository internalRepository;
    /**
     * Operation count. Necessary for autoSave().
     */
    private int operationCount;

    /**
     * Creates a new repository by loading saved data, if available.
     */
    public ToDoRepositoryWithJSON() {
        if (DEFAULT_FILE.exists()) {
            String data = getDataFrom(DEFAULT_FILE);
            internalRepository =
                    GSON.fromJson(data, InMemoryToDoRepository.class);
        } else {
            internalRepository = new InMemoryToDoRepository();
        }
        operationCount = 0;
    }

    /**
     * Reads in all the data from the given file (which MUST exist) and
     * returns it in the form of a String.
     *
     * @param file The File to read.
     * @return A String containing all the data in the File.
     */
    private static String getDataFrom(File file) {
        try {
            Scanner scanner = new Scanner(file);
            scanner.useDelimiter("\\A");    // read everything
            return scanner.next();
        } catch (FileNotFoundException e) {
            // ignore because file must exist
        }
        return "";
    }

    /**
     * Writes the contents of the internalRepository to the default file.
     */
    @Override
    public void save() {
        try (BufferedWriter writer = new BufferedWriter(
                new FileWriter(DEFAULT_FILE, false) // overwrite the file
        )) {
            writer.write(GSON.toJson(internalRepository));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public List<ToDoItem> findAll() {
        autoSave();
        return internalRepository.findAll();
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public ToDoItem findById(Long id) {
        autoSave();
        return internalRepository.findById(id);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public Long insert(String text) {
        autoSave();
        return internalRepository.insert(text);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public void update(ToDoItem toDoItem) {
        autoSave();
        internalRepository.update(toDoItem);
    }

    /**
     * {@inheritDoc}
     *
     * @return {@inheritDoc}
     */
    @Override
    public void delete(ToDoItem toDoItem) {
        autoSave();
        internalRepository.delete(toDoItem);
    }

    /**
     * Automatically saves progress after a certain number of operations.
     */
    private void autoSave() {
        operationCount++;
        if (operationCount % AUTO_SAVE_COUNT == 0) {
            save();
        }
    }
}

ToDoApp.java

package ml.cristatus.todo;

import ml.cristatus.todo.model.ToDoItem;
import ml.cristatus.todo.repository.ToDoRepository;
import ml.cristatus.todo.repository.ToDoRepositoryWithJSON;

import java.io.PrintStream;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;

/**
 * The main entry point for the Application. For now, this is just a CLI.
 *
 * @author Subhomoy Haldar
 * @version 0.3
 */
public class ToDoApp {
    public static void main(String[] args) {
        System.out.println(TITLE);
        REPL(new Scanner(System.in), System.out);
    }

    private static final String TITLE = "\n\n========ToDo App========\n\n";
    private static final String HELP = "The following commands are recognised:" +
            "\n - help\n\tDisplay this help message." +
            "\n - add <name>\n\tAdd a new Todo task with the given name and " +
            "also displays its corresponding ID." +
            "\n - get <id>\n\tDisplays the task with the given id." +
            "\n - mark <id>\n\tToggles the given task as completed or " +
            "incomplete." +
            "\n - print\n\tDisplays all tasks in order of their creation." +
            "\n - update <id> <new text>\n\tUpdates the item with the given " +
            "id to store the new text." +
            "\n - del <id>\n\tDeletes the task with the given id." +
            "\n - clear\n\tDeletes all COMPLETED tasks." +
            "\n - burn\n\tDeletes ALL tasks and empties the list." +
            "\n - exit\n\tExit the program.";
    private static final String PROMPT = ">> ";
    private static final String ABSENT = "No item found with the given ID.";

    /**
     * The "Read, Evaluate, Print, Loop" (REPL) method.
     *
     * @param in  The input source.
     * @param out The {@link PrintStream} to print to.
     */
    private static void REPL(Scanner in, PrintStream out) {
        ToDoRepository repository = new ToDoRepositoryWithJSON();
        out.println(HELP);
        out.println("\n");
        printAll(repository, out);
        //noinspection InfiniteLoopStatement
        while (true) {
            out.print(PROMPT);
            processInput(repository, in, out);
        }
    }

    /**
     * The method that is responsible for the processing of commands and
     * delegating control to the necessary methods.
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void processInput(ToDoRepository repository,
                                     Scanner in,
                                     PrintStream out) {
        String command = in.next().toLowerCase();
        switch (command) {
            case "help":
                out.println(HELP);
                break;
            case "exit":
                repository.save();
                System.exit(0);
            case "add":
                addItem(repository, in, out);
                break;
            case "get":
                ToDoItem item = input(repository, in);
                out.println(item != null ? item : ABSENT);
                break;
            case "mark":
                mark(repository, in, out);
                break;
            case "update":
                update(repository, in, out);
                break;
            case "del":
                delete(repository, in, out);
                break;
            case "print":
                printAll(repository, out);
                break;
            case "clear":
                clear(repository, out);
                break;
            case "burn":
                burn(repository, out);
                break;
            default:
                out.println("Unrecognised command. Try again.");
        }
    }

    /**
     * Tries to input a valid ID and returns the corresponding ToDoItem in
     * the repository. If anything fails, it returns null.
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @return The ToDoItem corresponding to the ID inputted.
     */
    private static ToDoItem input(ToDoRepository repository, Scanner in) {
        try {
            Long id = in.nextLong();
            return repository.findById(id);
        } catch (InputMismatchException exception) {
            in.nextLine();  // skip the line, start afresh
            return null;
        }
    }

    /**
     * Prints all the items in the list.
     *
     * @param repository The repository to work with.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void printAll(ToDoRepository repository,
                                 PrintStream out) {
        List<ToDoItem> items = repository.findAll();
        if (items.isEmpty()) {
            out.println("No tasks defined. Add some to get started.");
            return;
        }
        out.println("The tasks are :");
        for (ToDoItem toDoItem : items) {
            out.println(toDoItem);
        }
    }

    /**
     * Adds a new item to the repository.
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void addItem(ToDoRepository repository,
                                Scanner in,
                                PrintStream out) {
        String next = in.nextLine().trim();
        Long newId = repository.insert(next);
        out.println("New item added with ID = " + newId);
    }

    /**
     * Toggles the state of the item with the input ID (if valid).
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void mark(ToDoRepository repository,
                             Scanner in,
                             PrintStream out) {
        ToDoItem item = input(repository, in);
        if (item == null) {
            out.println(ABSENT);
            return;
        }
        item.setCompleted(!item.isCompleted());
        out.println("State of item with ID = " + item.getId() + " flipped.");
        repository.update(item);
    }

    /**
     * Updates the item with the input ID (if valid).
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void update(ToDoRepository repository,
                               Scanner in,
                               PrintStream out) {
        ToDoItem item = input(repository, in);
        if (item == null) {
            out.println(ABSENT);
            return;
        }
        String name = in.nextLine().trim();
        if (name.isEmpty()) {
            out.println("Please enter some text for the update.");
            return;
        }
        item.setName(name);
        out.println("Updated item #" + item.getId() + ": " + item);
        repository.update(item);
    }

    /**
     * Deletes the item with the input ID (if valid).
     *
     * @param repository The repository to work with.
     * @param in         The input source.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void delete(ToDoRepository repository,
                               Scanner in,
                               PrintStream out) {
        ToDoItem item = input(repository, in);
        if (item == null) {
            out.println(ABSENT);
            return;
        }
        out.println("Deleted \"" + item + "\"");
        repository.delete(item);
    }

    /**
     * Clears all the completed tasks from the repository.
     *
     * @param repository The repository to work with.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void clear(ToDoRepository repository,
                              PrintStream out) {
        int count = 0;
        for (ToDoItem item : repository.findAll()) {
            if (item.isCompleted()) {
                repository.delete(item);
                count++;
            }
        }
        out.println("List cleared. Deleted " + count + " items.");
    }

    /**
     * "Burns" the repository, purges it.
     *
     * @param repository The repository to purge.
     * @param out        The {@link PrintStream} to print to.
     */
    private static void burn(ToDoRepository repository,
                             PrintStream out) {
        int count = 0;
        for (ToDoItem item : repository.findAll()) {
            repository.delete(item);
            count++;
        }
        out.println("List emptied. Deleted " + count + " items.");
    }
}

I know that ToDoApp is hideous. I too am not happy with the processInput() method. I welcome comments and suggestions on every aspect. Thank you.

\$\endgroup\$
5
\$\begingroup\$

Here are a few observations from me:

ToDoItem.java

/**
 * Set the completion status of this task.
 *
 * @param completed The new status to be set.
 */
public void setCompleted(boolean completed) {
    this.completed = completed;
}

My personal view in methods like this is that instead of making them setters/mutators, you turn them into actions/features. So instead of having a setCompleted that takes a boolean from a client component, I think it would be a bit more sensible to have something like:

public void complete() {
    this.completed = true;
}

This way, you have more control over the internal state of a given class within the context of a given method, while limiting the method's behavior to something that's more specific. It also makes the code a little more traceable in that if there is no requirement that says a completed TodoItem can go back to being non-completed, you don't have to enable such state transition.

I think it's also somehow more "poetic" to have an invocation like task.complete() compared to task.setComplete(true).

InMemoryToDoRepository.java

/**
 * This class does NOT provide a saving mechanism.
 */
@Override
public void save() {
    // no save mechanism here
}

I'm not really sure, but somehow I feel like this can be an instance of a refused bequest, or some sort of breach of contract. I think you have few options here:

  • Remove save from ToDoRepository interface, and make it a specialization of ToDoRepositoryWithJSON.
  • Extend ToDoRepository to another interface, say, SaveCapableToDoRepository, define the save method in there and have ToDoRepositoryWithJSON implement the specialized interface.
  • Remove save from ToDoRepository, define a separate interface, say SavingRepository, with a save method in it, and have ToDoRepositoryWithJSON implement both ToDoRepository and the new one.

If you don't necessarily agree with this, I think you should at least throw an UnsupportedOperationException, like:

@Override
public void save() {
    throw new UnsupportedOperationException("You can't use save here.");
}

Normally, I do this for methods I have yet to implement while I work on specified methods one by one.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ One may have the requirement of unmarking a task if they've already marked it - by accident. After all, it's just a CLI. ;-) \$\endgroup\$ – Hungry Blue Dev May 22 '16 at 15:16
  • 1
    \$\begingroup\$ In that case, I would even use mark and unmark as method names. This is in line with the concept of ubiquitous language in Domain Driven Design. But then you have to consider whether mark-ing a ToDoItem means complete-ing it. It's really up to your requirements. \$\endgroup\$ – Psycho Punch May 22 '16 at 15:21
4
\$\begingroup\$

TodoItem.java

  • The following constraint is not enforced by the class TodoItem:

    A ToDoItem created later will have a higher ID.

As it is now, it is the responsibility of the clients (in this case the repositories), to take care of this. Also, it is only true for entities created within the same repository. So, I would recommend either moving the description of the constraint to the repositories (this solution would still have another disadvantage, i.e., that it prevents passing TodoItem's to ToDoRepository.insert -- more on that below), or really make it the responsibility of this class to enforce that. One possible solution is the following: ID should not be a constructor parameter, but should be assigned to private static long actId, increased in every call to the constructor:

public ToDoItem(String name) {
    this.name = name;
    this.id = actId++;
}

(Of course, you can make it atomic as well, if it should be thread-safe.) Another possible solution would be to have a TodoItemFactory, which takes care of creating TodoItem's. (In this case, I recommend somehow hiding the constructor of TodoItem, e.g. make it package private, and put the TodoItem in the same package as the factory. Or have the factory return only an interface which the internal TodoItem implements.)

  • I do not recommend documenting every getter/setter, where it is obvious what they do.

  • equlals method: possible NPE, if other is null, or this.name is null.

ToDoRepository.java

  • I recommend simplifying the JavaDoc. Instead of:

The {@link #save() save()} functionality is a useful and sought after feature, but some Repositories may choose not to implement it.

You can write e.g.: "The implementation of {@link #save() save()} function is optional." Same holds for the JavaDoc of the method itself.

  • I suggest changing the signature of the insert function, to expect a TodoItem instead of just a String. In this way, the interface of the repository is decoupled from the details of how a TodoItem looks like. (Imagine, that later you want to add new info to the TodoIteam, e.g. a Date dueDate. With the current solution, you would have to change the interface, along with every class implementing it.) In this way, it also won't be the responsibility of the repositories, to enforce the order of the ID's anymore (c.f. remark for TodoItem.java above).

InMemoryToDoRepository.java

  • In case you choose to keep the ID counting the responsibility of the repositories: I would not reset the counter if all entities are removed. What if another client still has access to one of the old TodoItems? You could end up violating the constraint that bigger ID means created later. (On the other hand, if you do remove ID counting from the repositories, you do not need the reset method either.)

ToDoRepositoryWithJSON.java

  • Nice solution of reusing the other repository, and not implementing everything from scratch again.

  • I would recommend making the directory and the file name a parameter, not hard-coded.

  • JavaDoc for internal variables: I would omit it, since I do not think it adds aything that it is not obvious (but increases the code length and maintenance effort).

  • In getDataFrom, the exception is swallowed: // ignore because file must exist. I recommend never-ever to do such a thing. While, based on the program logic, this affirmation is true (we first checked the existence of the file), there might be unexpected conditions (e.g. what if someone deletes the file exactly between the moment that its existence was checked and that you try to read from it? Ok, I guess the OS would not allow it in this case, because you have the file open all the time, still you do get the idea.) Also, the code might be refactored later and getDataFrom could end up being called from a point, where file existence is not checked. Long story short: I recommend logging the exception, or at least writing it to stderr. That might help a lot in the future, to debug strange issues ;)

  • autoSave: here I would reset the operationCount in case the necessary number is reached (otherwise, it might once overflow). Also, I would rename the function to autoSaveIfNeeded, to better reflect what it actually does, and thus make the code more understandable.

ToDoApp.java

  • I find the REPL pattern really nice :)
  • Instead of the endless loop + System.exit, I would recommend having a way of interrupting a loop (e.g., with an instance variable shouldExit that the REPL function sets, in case the input is "exit"). In this way, you can also get rid of the hint to your source code checker.
  • input: The name is a bit confusing. I would separate this function into two: the first one (let's call it readNumber) should only deal with reading a number from the input. The second one (getTodoItemById) should fetch the TodoItem with the given id from the repository. (Btw, also here, I would give feedback to the user if the wrong format of the number causes an exception.). Since input is used many times, you could have a utility function which then calls readNumber and getTodoItemById, e.g. fetchItem, to do the same as input does now.

  • Possible improvement of the processInput method (since you mentioned you are not happy with it): you could introduce a Command interface, with an execute(ToDoRepository repository, Scanner in, PrintStream out) method, and then implement it for each command (those that do not need all parameters would simply ignore the suprfluos ones). After that, you can put an instance of all the commands in a hashmap, where the key is the name of the command. Then, in processInput you just need to look up the command based on the name, and call its execute method.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Nice answer. +1. At some point I did have the insert() method take a ToDoItem but then I had to expose the ID to possible change by external code. I will certainly try to bring it back now. \$\endgroup\$ – Hungry Blue Dev May 22 '16 at 14:43

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.