Skip to content

Instantly share code, notes, and snippets.

@oschmid
Last active March 12, 2020 19:55
Show Gist options
  • Save oschmid/3ee4c21525ef9082390ba469c897d7cf to your computer and use it in GitHub Desktop.
Save oschmid/3ee4c21525ef9082390ba469c897d7cf to your computer and use it in GitHub Desktop.
To Do List Design Exercise
public class Task {
public Status status;
public String description;
public Optional<Integer> priority;
public Visibility visibility;
}
public enum Status {
TODO, IN_PROGRESS, UNDER_REVIEW, BLOCKED, DONE
}
public enum Visibility {
PRIVATE, FRIENDS
}
public class ToDoList {
private final UserId owner;
private final List<Task> tasks;
private final Set<UserId> friends;
private final List<Change> history;
// includes private as well as public
public int getTotal();
// returns tasks visible to user
public ImmutableList<Task> getTasks(UserId user);
// rewinds change history and returns tasks visible to the user
public ImmutableList<Task> getTasksSnapshot(Instant instant, UserId user);
public void add(Task task, UserId user);
public void update(Task oldTask, Task newTask, UserId user);
public void delete(Task task, UserId user);
}
public interface Change {
public Instant getTimestamp();
public ImmutableList<Task> undo(ImmutableList<Task> tasks);
}
public class Add implements Change;
public class Update implements Change;
public class Delete implements Change;
// getTasks() can be filtered with:
public Predicate<Task> onlyState(State state);
// getTasks() can be sorted with:
public Comparator<Task> byPriority();
public class UserId {
private final UUID uuid;
}
public class User {
public final UserId id;
public String username;
public final ToDoList todoList;
}
public class App {
private final Map<UserId, User> users;
private final Map<String, UserId> usernames;
User login(String username, String password);
User getUser(UserId userId);
User getUser(String username);
Integer getCountOfUsersWithTasksMatching(String description);
}
@oschmid
Copy link
Author

oschmid commented Mar 12, 2020

Initial Design

Pretty straight forward. I chose to use "TODO" instead of "not done" as names containing negation can be confusing. Tasks are immutable so instead of updating in place, changes (such as marking items as done) are made by replacement using update(old, new).

First round of feature requests

Added more values to the State enum. One alternative I considered was to make State a class and allow custom states per ToDoList. But this seemed like overkill. Also added optional priorities. The UI can filter and sort to-do items by calling ToDoList.getTasks() and then using Java streams along with the onlyState() Filter and byPriority() Comparator to filter and sort tasks before displaying them.

Second round of feature requests

Added a task Visibility and updated the methods of ToDoList to require the UserID of the user making each change or viewing the list of tasks. Internally it'll compare this UserID to the owner and set of friends to determine what to show. Since we now have users, I added an App class to handle login and looking up the ToDoList of a friend. Currently the user would have to know their friend's UUID to add them or look them up but adding usernames and a lookup by name is pretty straight forward. UUID is preferable to just using a String since the user may want to change their username.

Third round of feature requests

Counting the users with the same task descriptions is a strange (and insecure) feature but easy enough to add to App. I forgot to add it but we can have a list of Users as our internal data structure. The spec says to count the tasks of all users so I'm assuming private tasks are counted too. Though this may change to include only our friends or only public tasks. If so, getCountOfUsersWithTasksMatching() may change and include a UserId param.
Because of my decision to return an ImmutableList<Task> rather than allow editing in place, adding change history is straight forward. add(), update(), and delete() can add concrete implementations of the Change interface to the change history list and getTasksSnapshot() can rewind that list until the desired Instant in time. There's no way of retroactively adding history to ToDoLists that already exist so the earliest snapshot will just be the list as it existed when the history feature was introduced.

(Final cleanup)

I changed the name of State to Status since it fits better, added a missing getTimestamp() method to the Change interface. And a map to store users and usernames in App.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment