Problem:
Write a small archiving program that stores students' names along with the grade that they are in.
In the end, you should be able to:
- Add a student's name to the roster for a grade.
- Get a list of all students enrolled in a grade.
- Get a sorted list of all students in all grades. Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted
alphabetically by name.Note that all our students only have one name. (It's a small town, what do you want?)
Code:
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.ArrayList;
import java.util.Collections;
public class School {
private final Map<Integer, Set<String>> studentRecord;
public School() {
studentRecord = new HashMap<>();
}
public void add(String name, int grade) {
Set<String> names = Optional.ofNullable(
studentRecord.get(grade)).orElse(new TreeSet<>());
names.add(name);
studentRecord.put(grade, names);
}
public Map<Integer, Set<String>> db() {
return Collections.unmodifiableMap(studentRecord);
}
public Set<String> grade(int grade) {
return Collections.unmodifiableSet(Optional.ofNullable(
studentRecord.get(grade)).orElse(new TreeSet<>()));
}
public Map<Integer, List<String>> sort() {
Map<Integer, List<String>> studentGradeMap = new HashMap<>();
for (Map.Entry<Integer, Set<String>> entry : studentRecord.entrySet()) {
studentGradeMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return Collections.unmodifiableMap(studentGradeMap);
}
}
Test suite:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SchoolTest {
private final School school = new School();
@Test
public void startsWithNoStudents() {
assertThat(school.db()).isEmpty();
}
@Test
public void addsStudents() {
school.add("Aimee", 2);
assertThat(school.db().get(2)).contains("Aimee");
}
@Test
public void addsMoreStudentsInSameGrade() {
final int grade = 2;
school.add("James", grade);
school.add("Blair", grade);
school.add("Paul", grade);
assertThat(school.db().get(grade)).hasSize(3).contains("James", "Blair", "Paul");
}
@Test
public void addsStudentsInMultipleGrades() {
school.add("Chelsea", 3);
school.add("Logan", 7);
assertThat(school.db()).hasSize(2);
assertThat(school.db().get(3)).hasSize(1).contains("Chelsea");
assertThat(school.db().get(7)).hasSize(1).contains("Logan");
}
@Test
public void getsStudentsInAGrade() {
school.add("Franklin", 5);
school.add("Bradley", 5);
school.add("Jeff", 1);
assertThat(school.grade(5)).hasSize(2).contains("Franklin", "Bradley");
}
@Test
public void getsStudentsInEmptyGrade() {
assertThat(school.grade(1)).isEmpty();
}
@Test
public void sortsSchool() {
school.add("Jennifer", 4);
school.add("Kareem", 6);
school.add("Christopher", 4);
school.add("Kyle", 3);
Map<Integer, List<String>> sortedStudents = new HashMap<Integer, List<String>>();
sortedStudents.put(6, Arrays.asList("Kareem"));
sortedStudents.put(4, Arrays.asList("Christopher", "Jennifer"));
sortedStudents.put(3, Arrays.asList("Kyle"));
assertThat(school.sort()).isEqualTo(sortedStudents);
}
}
Note:
As personal opinion I found this exercise horrible, the implementation was not that difficult but the API expected in the test suite were horrible. The client knows more than expected of the internal date structure used and of course Law of Demeter.
Design Decisions:
The question expected some kind of mapping of grades with names so, the choice for Map
seemed obvious to me. Now, the expectation was to have a sorted list of names so, I had two choices:
- Have a unique list of names and keep them sorted if any modifications are made.
- Have a sorted set which takes care of the above problem. But as the client code expected the List in the
sort
method I have to do the conversion there.
Questions:
Today while coding I got one weird issue, I was confused about the data structure for studentRecord
so I toggled between Set
and List
couple of times, but every time that change was propagated everywhere in the code for eg: return type and all, is it some code smell or bad OO design?
Last but not least I kept the studentRecord
field final and every public methods returns immutable view of it to prevent any unintentional modifications.
Reference: Exercism