Skip to content

A reflection on design

Overview

Let's review the design of CourseReVU app:

Is this a good design? Let's answer in terms of the principles we've learned so far: cohesion and coupling.

Recall

We want a design where entities (classes) are highly cohesive and loosely coupled.

Cohesion

We could have had the Course class take on the responsibilities of JdbcCourseCrudPersister; after all, operations like addCourse, updateCourse, ... are about a course. Instead, we have extracted the persistence mechanism out of the Course into its own entity. This is a good thing! It is in-line with "increasing cohesion;" the Course class is only responsible to model a course; it does not need to know about some type of database or other persistence operations. If we change the persistence process, the Course class remains intact (and therefore other entities in the code that collaborate with Course have no reason to change).

Coupling

The JdbcCourseCrudPersister collaborates with Course; there is a dependency relationship between them. This dependency is unavoidable; it is the price we pay to increase cohesion. The resulted coupling is not that bad though; JdbcCourseCrudPersister only relies on the public methods of Course. The public methods (the interface of) a class is expected to be stable (ideally, it never changes).

The JdbcCourseCrudPersister also collaborates with Connection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class JdbcCourseCrudPersister {
  private Connection conn;

  // PRE: conn is an open connection to a SQLite database with Courses table.
  public JdbcCourseCrudPersister(Connection conn) {
    this.conn = conn;
  }

  // other methods not shown here.
}

There is a tighter coupling here, in particular, JdbcCourseCrudPersister does not make its own Connection and relies on a client to pass it to its constructor. The JdbcCourseCrudPersister then assumes (perhaps naively) the conn is an open connection to a SQLite database with Courses table. It further assumes Courses table has a particular structure (three columns id, name, and url where id is the primary key). We could perhaps let the constructor of JdbcCourseCrudPersister take more responsibility here:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class JdbcCourseCrudPersister {
  private final Connection conn;

  // PRE: uri is a valid Universal Resource Identifier where conn 
  //    can use it to open a connection to a database.
  public JdbcCourseCrudPersister(String uri) {
    conn = DriverManager.getConnection(URI);

    String sql = "CREATE TABLE IF NOT EXISTS Courses(" + 
                    "id INTEGER PRIMARY KEY," + 
                    "name VARCHAR(30) NOT NULL," + 
                    "url VARCHAR(200)" + 
                  ");";
    PreparedStatement pst = conn.prepareStatement(sql);
    pst.execute();
  }

  // other methods not shown here.
}

The two approaches shown above are both acceptable; the first approach is more common and it is known as dependency injection where you pass a resource into a class that uses it as a constructor argument. The second approach lets the class to make the right implementation for itself. In the second approach, the constructor can get quite unwieldy.

Takeaway

It is considered a design pattern to provide an abstraction between database and the rest of the application. We have done this (to a good extend) by extracting the persistence procedure from the Course class into JdbcCourseCrudPersister. In design lingo, a class like JdbcCourseCrudPersister is called a data access object or DAO. We will explore this concept in greater detail in the readings that follow.