Home CPSC 330

Class Design

Overview

For any program or system, there are countless ways to design it. Some designs may be just as good as others, but some will lead to programs that are hard to write, maintain, debug and test.

There are few absolute rules in class design, but several tips that will help avoid common pitfalls.


Single Responsibility Principle

The single responsibility principle states that every class should have a single responsibility.

Each part of the class should be related to that one responsibility.

Reasons for this:

What portions of this class do not belong?


class Contact {
    public Contact(String name, String number, String email);
    
    @Override
    public String toString();

    public void setName(String name);
    public void setNumber(String number);
    public void setEmail(String email);

    public String getName();
    public String getNumber();
    public String getEmail();

    public void sendEmail();

    private string name, setNumber, email;
}

What about this one?


class Line {
    public Line(double x0, double y0, double x1, double y1);

    public double distance();
    public double slope();
    public boolean parallelTo(Line other);

    public void draw(Canvas c);

    private double x0, y0, x1, y1;
}

Analyzing Interfaces

Each classes public methods provide the interface to other code. Designing a good interface makes your classes easy to use.

The following properties make a good interface:


Coupling

Coupling refers to how two parts of a program are dependent on each other. If two parts interact together, they are coupled.

If they interact very closely together, such that changing one necessitates changing the other, they are tightly coupled.

Classes can also be loosely coupled if they don't depend on each other much, or are used through an interface.


The Law of Demeter

The law of Demeter is a rule for writing methods to reduce coupling.

The law states that a method of an object should only refer to:

The code below folows this rule. The method show only accesses objects passed in (format), part of the class (month, day, year) or created in the method (names):


public class Date {
  // ...

  public void show(DateFormat format) {
    switch(format) {
      case LONG:
        String [] names = {"January", "February", "March", "April", "May",
          "June", "July", "August", "September", "October", "November","December"};
        System.out.printf("%s %d, %d", names[month - 1], day, year);
        break;
      case SHORT:
        System.out.printf("%d/%d/%d", month, day, year);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  // ...
}

The example below violates the rule by accessing a String stored inside of the Date object:


public class Calendar {
  // ...

  public void addEvent(Date d) {
    if(d.getMonthName().equals("January")) {

    }
  }

  // ...
}

In general, each variable should only be accessed with one dot:


a.b()
as opposed to:

a.b().c()

The purpose of the law is to limit the coupling amongst objects.

In the code above, the Calendar class is tying itself to not only the Date class but also the String class.

Class interfaces should be constructed so that code using them does not need to interact with sub-objects directly.


De-Coupling with an Interface

In the following example, the StudentList class is coupled with the Database class:

class StudentList {
  private Database database;

  public void add(String name) {
    students.addRow("students", name);
  }
}

class Database {
  // ...

  public void addRow(String table, String value) {
    // ...
  }
}

If we make a change to the database class, the StudentList class may be affected. We can de-couple these classes using a generic interface:


class StudentList {
  private Storage students;

  public void add(String name) {
    students.store(name);
  }
}

interface Storage {
  public void store(String data);
}


class Database implements Storage {

  public void store(String data) {
    addRow("default", data);
  }
  
  public void addRow(String table, String value) {
    // ...
  }
}

Top Down vs. Bottom Up Design

When designing and implementing a program or system, there are two general approaches:

What are the advantages of each?

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.