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
and using \(\Sigma\) for all of the programs data (collectively called the store), the program’s execution is modelled as
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
and using \(\sigma\) for the input to \(f_{1}\), an evaluation that applies the functions in order becomes
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
-
design our data and create examples (instances)
-
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
-
Given as an argument.
-
Returned as a result of a method call.
-
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
-
Primitive types (start with a lower case letter)
-
e.g.,
int
,bool
,float
…
-
-
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.
-
The JDK documentation index page.
-
The documentation for String.
-
The documentation for Integer.
/** (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
To denote a class we use a rectangular shape with three sections, each section separated by a horizontal line:
-
The first section holds the class name
Person
. -
The second section lists all fields and their types
-
we use
-
forprivate
fields -
we use
+
forpublic
fields
-
-
The third section lists all the methods and their signatures.[5]
-
we use
-
forprivate
methods -
we use
+
forpublic
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
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
-
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.
-
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.
-
-
Translate your class diagram to Java code.
-
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.