Relevant Reading Material

Chapters From How to Design Classes (HtDC)

  • Chapter 1

  • Chapter 2

  • Chapter 3

Programming Paradigms

A programming paradigm is a general approach or philosophy that can be used to implement a program. You can think of a programming paradigm as a programming style that indicates the approach on how we implement our solution in a programming language.

Typically, programming languages are design with at least one of these paradigms in mind. As such certain programming languages are better suited to certain programming styles. It is however typical for a language to accommodate more than one programming style. It is also possible to use one programming style to encode another. More on this later in the semester.

Imperative (or procedural) paradigm

In the imperative (or procedural) paradigm our programs are made up of procedures and data. Procedures are made up of a sequence of statements, where each statement may alter our existing data in place (typically this means there is no return value from our procedure). We can then think of our program’s execution as a machine that executes each statement in the appropriate sequence and a store that holds all our data that each statement will alter (or mutate).

Using an informal abstract syntax, for a program with the following sequence of statements

\[\begin{array}{c} S_{1}; \\ S_{2}; \\ \vdots \\ S_{n}; \\ \end{array}\]

and using \(\Sigma\) for all of the programs data (collectively called the store), the program’s execution is modelled as

\[\Sigma_{0} \xrightarrow{S_{1}} \Sigma_{1} \xrightarrow{S_{2}} \Sigma_{2} \cdots \xrightarrow{S_{n}} \Sigma_{n}\]

Some popular procedural languages are C, Pascal, Ada etc.

Applicative (or functional) paradigm

In the applicative (or functional) paradigm our programs are made up of functions and data as values. Functions are made up of one expression. Our functions take as input values and return as output values. A functional program’s execution is an evaluation, much like the evaluation process in simple mathematical expressions.

Using a similar informal abstract syntax, for a program with the following set of functions

\[\begin{array}{c} f_{1} \\ f_{2} \\ \vdots \\ f_{n} \\ \end{array}\]

and using \(\sigma\) for the input to \(f_{1}\), an evaluation that applies the functions in order becomes

\[ f_{n}(\cdots f_{2}(f_{1}(\sigma)) \cdots)\]

Each function will return a new \(\sigma\) that is then passed on as input to then function application.

Some popular functional languages are Lisp, Scheme, ML, Haskell etc. All of the teaching languages in CS5001 were functional languages.

Object Oriented Paradigm

In the Object Oriented (OO) paradigm our programs are made up of objects that communicate with each other by sending messages. More specifically, in class-based OO we have the notion of a class that is made up of fields (data) and methods (messages that this class understands). From a class we can then create an object that has its own set of fields but shares the same methods.

A method is made up of a sequence of statement and/or expressions that may manipulate the objects's fields and may return an object as a result of executing that method.

Some popular class-based OO languages are, Java, C++ , C#, Eiffel, Smalltalk.

Other paradigms

This class will at first focus on OO and then we will use a mixture of OO, procedural and functional styles all within Java. Most languages accommodate a set of paradigms although a language is typically designed to support well a few paradigms. The term multi-paradigm is commonly used to refer to languages support more that one paradigm.

Some other programming paradigms that we will not cover in this class are

  • Logic Program, Prolog

  • Aspect Oriented, AspectJ

  • Event-Driver

  • Actor-Oriented

  • Metaprogramming

Shift your thinking

In CS5001 we had data and functions that operate on data. Typically we would

  1. design our data and create examples (instances)

  2. design functions that take these data as inputs and return data as outputs

Data and functions in Racket are two independent entities. The dependency between which data is manipulated by which function(s) is defined by the signatures of our functions.

With Object Orientation data and functions (called methods) are packaged together into a class. So a class has data and methods. These methods have access to the data inside the class. The dependency between which data is manipulated by which method is defined in two parts, the signatures of the methods and the location of the methods.

Examples are instance of classes (called objects) and these objects hold inside of them data and methods. We thus talk about the methods that an object understands (or has available). Computation is then composed by creating objects and asking them to evaluate (run) their methods.

Values in Java

A value can be

  1. Given as an argument.

  2. Returned as a result of a method call.

  3. Stored

In Racket we had Number String Symbol Images etc. These were data definitions that are either provided to us by Racket or data definitions that we defined as comments. In Java data definitions can be captured as types and they are not comments but rather part of the Java language.

A value in Java is associated with a type. For now you can think of a type as a category, or a set, that indicates the values and operations that these values understand.

Java has two kinds of types

  1. Primitive types (start with a lower case letter)

    • e.g., int, bool, float …​

  2. Reference types (start with a capital case letter)

    • e.g., Object, LinkedList, String, Integer …​

For now your code is not allowed to use primitive types. You are not allowed to use arrays either. Your code should only use reference types.

From define-struct to class

In Racket, we were able to create new values made up of a finite set of slots. The slots could then store other values. Here is an example in Racket that captures information for a Person, specifically the first and second name of a person as well as their age.

(define-struct person (first second age))
;; A Person is (make-person String String PosInt)
;; INTERP: represents a person with their first, and second name
;;         and their age

Informally, we can draw a parallel to Java’s classes.

public class Person { (1)

    private String first;      (2)
    private String second;
    private Integer age;

}
1 We define a class using the keyword class. The keyword public is called a modifier and modifies the visibility of this class. Visibility here refers to the ability of code in other classes/files to "see" (use/refer to) this class
2 Slots from Racket are called fields in Java. In Java however we must specify the type and the name of the field. Informally the type maps to the Data Definition names that we used in Racket struct Data Definitions. The modifier private designates that this field is private to this class and can be accessed only from code that is within this class definition.

The keywords public and private will be explained in detail later in the course. For now your code should follow the rule

  • All classes are public

  • All fields are private

Constructors and selectors

In Racket a define-struct generated a set of functions that we used to create, access and test a value created from this structure.

;;;; CONSTRUCTOR
;;; make-person: String String PosInt -> Person


;;;; SELECTORS
;;; person-first : Person -> String

;;; person-second : Person -> String

;;; person-age : Person -> PosInt


;;;; Predicate
;;; person? : Any -> Boolean

In Racket we referred to operations as functions, in Java operations are referred to as methods.

Java does not automatically generate these operations on your data. In Java we need to code these operations. Java programmers also use a different naming convention for methods. Here is the mapping

Racket Java

Constructor

Constructor

Selector

Getter

Here is the updated Person class with a constructor and getters for each field. We will look at each new element in turn.

public class Person {

    private String first;
    private String second;
    private Integer age;

    /**
     * Instantiates a new person given its first and second name and
     * their age.
     *
     * @param first the first name of the person
     * @param second the second name of the person
     * @param age the age of the person
     */
    Person(String first, String second, Integer age){
        this.first = first;
        this.second = second;
        this.age = age;  // years
    }

    /**
     * Gets the person's first name.
     *
     * @return the first name of the person
     */
    public String getFirst() {
        return first;
    }

    /**
     * Gets the person's second name.
     *
     * @return the second name of the person
     */
    public String getSecond() {
        return second;
    }

    /**
     * Gets the person's age.
     *
     * @return the age of the person.
     */
    public Integer getAge() {
        return age;
    }
}

Let’s look at teach newly added code segment in turn.

Constructor

public class Person {

    private String first;
    private String second;
    private Integer age;

    /**   
     * Instantiates a new person given its first and second name and(1)
     * their age.
     *
     * @param first the first name of the person 
     * @param second the second name of the person(2)
     * @param age the age of the person
     */
    public Person(String first, String second, Integer age){  
        this.first = first;  (3)
        this.second = second;(4)
        this.age = age;
    }
1 A multiline comment in Java starts with /* and ends with */. This is equivalent to Racket’s #| and |#. Java comments can be free form text but Java comments can also deal with HTML. The Javadoc tool can be used to read all comments in your source code and generate documentation. A single line comment in Java starts with //, equiavelent to Racket’s ;
2 Javadoc allows you to provide documentation for the arguments using the @param annotation, followed by the argument’s name and then a sentence describing what this argument is for.
3 This line starts the definition of the constructor method. Constructor methods are special methods. The name of a constructor method must be the same name as the class name, i.e., Person. Arguments are then provided between normal parenthesis separated by a comma. For each argument we need to specify its type a space and a name, i.e., String first.
4 The body of the constructor method is enclosed in curly parenthesis. While writing the body of the constructor method, if we want to refer to the fields of the class we must use the special keyword this. While inside the file of a class, if we want to access a field of the enclosing class we use this then a dot `. and then the name of the field.

The constructor method has the responsibility to check the values passed to it and assign the appropriate value to the appropriate field of the newly created value.

Here is another rule to follow for this course

All constructor methods are public

Getter

    /**
     * Gets the person's first name.
     *
     * @return the first name of the person (1)
     */
    public String getFirst() { (2)
        return first; (3)
    }
1 Javadoc provides the annotation @return that allows us to annotate and comment on the method’s return value.
2 For a normal method, the signature consists of a modifier public, the type of the value to be returned by the method String, the name of the method getFirst then the list of arguments enclosed in normal parenthesis. In this case there are no arguments. The modifier public here indicates that this method is available to be called from inside the class but also from other classes.
3 In the method body if we want to return a value back to the caller we must use the keyword return followed by the value to return or an expression that will be first evaluated to obtain a value and then return that result.

Yet another rule for this course

All methods are public

The rest of the methods that we added in Person follow the same pattern as the one for the method getFirst(); they return the value associated with a field. These methods are called getter methods because they "get the value of a field for the current objec for the current objectt".

The names for getter methods in Java follow a naming convention, prepend the word get to the name of the field in CamelCase. We used CamelCase to write our Data Definition names in Racket.

Examples and Tests

In Racket we were able to create examples and tests. Our tests in Racket could use our examples and check using check-expect that we do in fact have the correct values inside our value. In Racket we could also use our interactions window to create some examples and test/inspect them.

(define-struct person (first second age))
;; A Person is (make-person String String PosInt)
;; INTERP: represents a person with their first, and second name
;;         and their age

(define JOHN (make-person "John" "Doe" 45))
(define MAGGIE (make-person "Maggie" "Flux" 23))

(check-expect (person-first JOHN) "John")
(check-expect (person-second JOHN) "Doe")
(check-expect (person-age JOHN) 45)

Java does not have an interactions window (yet!). Java also does not have a build-in check-expect. In order to write tests in Java we are going to use a library called JUnit to help as write and run tests.[1]

Examples and Tests in Java and JUnit are create in a separate class.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
public class PersonTest { (1) private Person john; private Person maggie; @org.junit.Before (2) public void setUp() throws Exception { (3) john = new Person("John", "Doe",45); (4) maggie = new Person("Maggie", "Flux", 23); } @org.junit.Test (5) public void getFirst() throws Exception { Assert.assertEquals(john.getFirst(), "John"); (6) Assert.assertEquals(maggie.getFirst(), "Maggie"); } @org.junit.Test public void getSecond() throws Exception { Assert.assertEquals(john.getSecond(), "Doe"); Assert.assertEquals(maggie.getSecond(), "Flux"); } @org.junit.Test public void getAge() throws Exception { Assert.assertEquals(john.getAge(), new Integer(45)); (7) Assert.assertEquals(maggie.getAge(), new Integer(23)); }
1 We create a new class that will contain all of our examples and tests for Person. The name for this new class is to append the word Test to the class name that we are going to test, in this case the class Person, giving us the final name PersonTest.
2 This is a Java annotation. Annotations are hints to the Java language and are used by JUnit to mark which methods are tests and which methods are to be called before/after we run our tests.
3 The method setup is marked to be executed before we run any tests. We use the setup method to create our examples and assign them to fields so that we can use them in our tests later
4 We are creating a value here that is the example for John, just like we did in Racket using define and we are assigning the value to the field john. This is similar to what we did in the constructor method for class Person.
5 This is another annotation from JUnit. org.junit.Test is used immediately above a method inside a test class to tag the method as a test that needs to be evaluated by JUnit.
6 JUnit provides a library for asserting (checking/verifying) that two values are the same.[2]. Assert.assertEquals() takes two arguments, much like check-expect, and checks that the two values are equal.
7 Observe that the second argument to Assert.assertEquals() is new Integer(45) and not 45. JUnit gets confused when it needs to deal with int and Integer types. [3]

You are free to organize your tests in any manner that you see fit. The typical pattern is to have at least one test method for each method in the class that you want to test.

Javadoc

Java provides a special tool that is responsible for reading your source code and generating HTML formatted documentation from your source code’s comments. The tool is called Javadoc.

All of the Java libraries use Javadoc for their documentation.

    /** (1)
     * Gets the person's first name.
     *
     * @return the first name of the person (2)
     */
    public String getFirst() {
        return first;
    }
1 To start a Javadoc comment we must use /**, notice the two stars! And end the comment with */
2 Javadoc provides special annotations for, parameters @param, return values @return.

We can also use HTML tags inside our Javadoc comments.

Java types will not always exactly match your Data Definitions. Any invariants, WHERE clauses, termination arguments, halting measures or any other conditions that you would like to express must be written inside Javadocs.

Diagrams

As part of designing our data for a given problem we will be using a notation for drawing diagrams. The graphical notation that we will use is based on UML. [4]

We use diagrams before we write any code. Diagramming helps developers discuss their ideas on how they intend to capture information from as data. Recall the class Person, here is the class diagram for Person

person2

To denote a class we use a rectangular shape with three sections, each section separated by a horizontal line:

  1. The first section holds the class name Person.

  2. The second section lists all fields and their types

    • we use - for private fields

    • we use + for public fields

  3. The third section lists all the methods and their signatures.[5]

    • we use - for private methods

    • we use + for public methods

Notice that in the class Person all fields are of a standard Java types String and Integer.

Class references and containment

As you already have seen in CS5001, data can be a lot more complex. Recall from Racket that we can have nested structures (ignore recursively nested structures for now).

Let’s consider the case of an online book store that would like to represent books. The online bookstore would like to store books and information about books, specifically

  • the title of the book

  • the author of the book (you may assume there is only one author)

  • the price of the book in dollars (you may assume it is always a rounded dollar amount)

The title of the book is essentially a string. The author of a book has a first and a last name. The price of the book is a rounded dollar amount, e.g., 5 (not 5.00 or 5.55).

Here one way that we can capture this information as data in Racket

(define-struct person (first second))
;; A Person is (make-person String String)
;; INTERP: represents a person with their first, and second name



(define-struct book (title author price))
;; A Book is (make-book String Person PosInt)
;; INTERP: represents a book with its title, author and price in
;;         dollars.


;;;; Examples

(define MILAN (make-person "Milan" "Kundera"))
(define ECO (make-person "Umberto" "Eco"))

(define ULB (make-book "The Unbearable Lightness of Being"
                       MILAN
                       9))

(define NOR (make-book "In the Name of the Rose"
                       ECO
                       10))

So let’s use a class diagram to figure out how we are going to capture these information about books for the online bookstore in Java using classes

bookstore

Observe how the arrow points from Book to Person to show that there is a containment relationship, i.e., a book contains an author. In OO lingo we call this a has-a relationship; an object of Book has-a Peron object inside of it.

These are the same arrows we use to draw on top of Data Definitions in Racket. We typically draw the containment arrows for classes that are not provided by the Java language.

Going from the UML class diagram to code should be straightforward.

IDEs and tools can take UML class diagrams and generate code but also take Java code and generate UML class diagrams. DO NOT USE THESE TOOLS. The point of this course is to teach you how to design and code your solutions. Once you master the topics of the course then you can rely on automated tools.

Here is the code for Person and Book in Java. Each definition is written in a separate file.

/**
 * Represents an author in the online bookstore
 */
public class Person {

    private String first;
    private String second;

    /**
     * Creates a new person with the given first and last name.
     *
     * @param first the person's first name
     * @param second the person's last name
     */
    public Person(String first, String second) {
        this.first = first;
        this.second = second;
    }

    /**
     * Getter for property 'first'.
     *
     * @return Value for property 'first'.
     */
    public String getFirst() {
        return first;
    }

    /**
     * Getter for property 'second'.
     *
     * @return Value for property 'second'.
     */
    public String getSecond() {
        return second;
    }
}
/**
 * Represent a book in the online bookstore
 */
public class Book {

    private String title;
    private Person author;
    private Integer price;

    /**
     * Creates a new Book with the given title, author and price.
     *
     * @param title the book's title
     * @param author the book's author
     * @param price the book's price
     */
    public Book(String title, Person author, Integer price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    /**
     * Getter for property 'title'.
     *
     * @return Value for property 'title'.
     */
    public String getTitle() {
        return title;
    }

    /**
     * Getter for property 'author'.
     *
     * @return Value for property 'author'.
     */
    public Person getAuthor() {
        return author;
    }

    /**
     * Getter for property 'price'.
     *
     * @return Value for property 'price'.
     */
    public Integer getPrice() {
        return price;
    }
}

Let’s add a method to Book that will increase the books price by a given amount.

We would like to add the ability to increase a book’s price by a given amount of dollars. The method should accept the amount to increase the price by, and return a new book with all the information of the original book but with the price increase.

Let’s do this in Racket first so that we can compare and contrast

;; A Book is a (make-book String Person Integer)
(define-struct book (title author price))
;; INTERP: represents a book with one author and its price

;; book-fn: Book -> ???
#;(define (book-fn book)
   ... (book-title book) ...
   ... (person-fn (book-author book)) ...
   ... (book-price book) ...)




;; A Person is a (make-person String String)
(define-struct person (first last))
;; INTERP: represents a person with their first and last name

;; person-fn: Person -> ???
#;(define (person-fn person)
   ... (person-first person) ...
   ... (person-last person) ...)


;;;; Signature
;; book-increase-price: Book Integer -> Book
;;;; Purpose
;; GIVEN: the book and the number of dollars to add to the book's price
;; RETURNS: the book with the updated price
(define (book-increase-price book increment)
 (make-book (book-title book)
            (book-author book)
            (+ increment (book-price book))))


;; Examples
(define AUTHOR1  (make-person "x" "y") )
(define BOOK-ORIGINAL (make-book "a" AUTHOR1 10))
(define BOOK-UPDATED (make-book "a" AUTHOR1 13))


;;;; Tests
(check-expect (book-increase-price BOOK-ORIGINAL 3)
              BOOK-UPDATED)

And here is the method added to the class Book.

  /**
   * Given the amount in dollars to increase the price return a new
   * book with all the information including the new calculated price.
   *
   * @param increase whole integer to add to price
   * @return new book with the same title and author and the new price
   */
  public Book increasePriceBy(Integer increase) {
    return new Book(this.title, this.author, this.price + increase);
  }

Please note that for now our code must create and return a new object.

All your methods must have a return type that is a reference type. You must always create a new object and return it. For now we writing Java programs in a functional style.

And here is the JUnit test class

import org.junit.Assert;

public class BookTest {

  private Person author ;
  private Book original;
  private Book updated;

  @org.junit.Before
  public void setUp() throws Exception {
    this.author = new Person("x", "y");
    this.original = new Book("a", this.author, 10);
    this.updated = new Book("a", this.author, 13);

  }

  @org.junit.Test
  public void increasePriceBy() throws Exception {
    Book actual = this.original.increasePriceBy(3);
    Assert.assertEquals(actual.getAuthor(), updated.getAuthor());
    Assert.assertEquals(actual.getTitle(), updated.getTitle());
    Assert.assertEquals(actual.getPrice(), updated.getPrice());
  }

}
For now, you can only use assertEquals for objects created from classes defined in the JDK. You cannot use assertEquals for any of the classes that you create. We will fix this later.

Designing Classes

  1. Read the problem and identify pieces of information that are relevant for the task at hand. These will probably end up being fields in your classes.

  2. Draw a class diagram. You can do this with pen and paper or use a tool if you prefer. Critic your class diagram and ensure that you are capturing all the information relevant to the problem.

    • You have capture what is needed, nothing more nothing less. "The truth, the whole truth and nothing but the truth".

    • The structure of your data is dictated by the structure/relationships of the information as used in the problem.

  3. Translate your class diagram to Java code.

  4. Make examples and tests.

You have seen similar steps before. These are your new steps for the first element of the Design Recipe, Data Design and Data Definitions. Next lecture we will introduce the Design Recipe for OO and Java in particular.


1. There are other testing libraries for Java, like TestNG, but for this course we will be using JUnit.
2. In Java equality is not as straigh forward as in Racket. We will cover equality in detail in the following weeks
3. Java introduced an automatic mechanism to turn int values to Integer values and vice versa called autoboxing. Autoboxing and method overloading (allowing the same name for 2 or more methods that have different arguments) confuses JUnit, so we have to be explicit. More on autoboxing and overloading later in the semester.
4. A good brief guide to UML is the book UML Distilled. You can also read this quick introduction to UML class diagrams.
5. Signatures in Java mean the method header that consists of the return type, the name of the method and the list of arguments as pairs of type and name separated by commas and enclosed in normal parenthesis.