Single-Responsibility Principle


New reports are continually needed in the student information system. You have been told that you must now produce three additional reports. And you can surmise that new reports will continue to be requested. You foresee the need to change the CourseSession class constantly as the reports are added.


One of the most basic design principles in object-oriented programming is that a class should do one thing and do it well. By virtue of doing this one thing, the class should have only one reason to change. This is known as the Single-Responsibility Principle.[4]

[4] [Martin2003].

Classes should have only one reason to change.


The one thing CourseSession should be doing is tracking all information pertinent to a course session. Adding the capability to store professor information for the course session is a motivation that is in line with the primary goal of the class. Producing reports such as the roster report, however, is a different motivation for changing the CourseSession class and as such violates the Single-Responsibility Principle.

Create a test class, RosterReporterTest, to demonstrate how a new, separate class named RosterReporter can be used to produce a roster report.

 package studentinfo; import junit.framework.TestCase; import java.util.*; public class RosterReporterTest extends TestCase {    public void testRosterReport() {       CourseSession session =          new CourseSession("ENGL", "101", createDate(2003, 1, 6));       session.enroll(new Student("A"));       session.enroll(new Student("B"));       String rosterReport = new RosterReporter(session).getReport();       assertEquals(          RosterReporter.ROSTER_REPORT_HEADER +          "A" + RosterReporter.NEWLINE +          "B" + RosterReporter.NEWLINE +          RosterReporter.ROSTER_REPORT_FOOTER + "2" +          RosterReporter.NEWLINE, rosterReport);    }    Date createDate(int year, int month, int date) {       GregorianCalendar calendar = new GregorianCalendar();       calendar.clear();       calendar.set(Calendar.YEAR, year);       calendar.set(Calendar.MONTH, month - 1);       calendar.set(Calendar.DAY_OF_MONTH, date);       return calendar.getTime();    } } 

The method testRosterReport is almost the same as it appeared in CourseSessionTest. The chief differences (highlighted in bold):

  • You construct an instance of RosterReporter with a CourseSession object as a parameter.

  • You now use class constants declared in RosterReporter instead of CourseSession.

  • testReport constructs its own CourseSession object.

You should also recognize and make note of the duplicationboth CourseSessionTest and RosterReporterTest require the createDate method. You will soon refactor this duplication away.

Add the new test to AllTests:

 package studentinfo; import junit.framework.TestSuite; public class AllTests {    public static TestSuite suite() {       TestSuite suite = new TestSuite();       suite.addTestSuite(StudentTest.class);       suite.addTestSuite(CourseSessionTest.class);       suite.addTestSuite(RosterReporterTest.class);       return suite;    } } 

Much of the work of getting the test to pass involves moving the code over from CourseSession. Do this incrementallydon't make any changes to CourseSession or CourseSessionTest until everything is working in RosterReporter.

 package studentinfo; import java.util.*; class RosterReporter {    static final String NEWLINE =       System.getProperty("line.separator");    static final String ROSTER_REPORT_HEADER =       "Student" + NEWLINE +       "-" + NEWLINE;    static final String ROSTER_REPORT_FOOTER =       NEWLINE + "# students = ";    private CourseSession session;    RosterReporter(CourseSession session) {       this.session = session;    }    String getReport() {       StringBuilder buffer = new StringBuilder();       buffer.append(ROSTER_REPORT_HEADER);       for (Student student: session.getAllStudents()) {          buffer.append(student.getName());          buffer.append(NEWLINE);       }       buffer.append(          ROSTER_REPORT_FOOTER + session.getAllStudents().size() +          NEWLINE);       return buffer.toString();    } } 

The bold code in the example above shows the significant differences between RosterReporter and the corresponding code in CourseSession.

To arrive at the code above, first paste the body of getreport from CourseSession directly into the corresponding method in RosterReporter. Then modify the pasted code to request the collection of students from CourseSession by sending the getAllStudents message instead of accessing it directly (since the method no longer executes in CourseSession). Since you removed getAllStudents in the previous lesson, you'll just have to add it back to CourseSession.

 class CourseSession {    ...    ArrayList<Student> getAllStudents() {       return students;    }    ... } 

Also, in order for code in RosterReporter to be able to send messages to the CourseSession object, it must store a CourseSession reference. You do this by assigning the CourseSession passed to the constructor of RosterReporter in the instance variable session.

Next, remove the report-related code from CourseSessionTest and CourseSession. This includes the test method testRosterReport, the production method getrosterReport, and the class constants defined by CourseSession. Rerun all tests.

The current class structure is show in Figure 3.1.

Figure 3.1. Class Diagram




Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net