Software Development Activities: specification, design & implementation, verification, evolution.
Object-Oriented Design (OOD) Process: an iterative process of discovery and refinement.
OOD works: classes represent stable problem domain concepts.
Heuristic Process: trial of errors, analysis, and refinement.
Skeleton Code: implement minimal parts or features of full system first.
Component Wise: implement one class or component at a time.
Object: an entity with state, behaviours to operate on the state with unique identity.
Class: an object’s blueprint.
Dependency: a class uses
another class.
Aggregation: a class has-a
object of another class (multiplicities).
Inheritance: a class is-a
subcategory of another class. (prefer composition over inheritance)
Coupling: two classes are coupled if one depends on the other. The less coupling, the better for better flexibility.
Wicked Problem: need good design to implement the system but need the system to know if you have a good design. (Contradiction)
Sloppy Design: make mistakes and adapt.
Heuristic Process: uses rules of thumb, trial and errors, analysis and refinement.
Continual Integration: a gradual growth of the system by continually integration changes.
Big Bang Integration: assemble the software at once.
Primitive Data Type: pass by value (copy)
Object Data Type: pass by reference (pointer)
Static Factory Method: a static method that creates and return a new object.
Immutable: when an object is created, it cannot be changed.
Anonymous Class: an unnamed class, uses for custom sorting, filtering files, and event callbacks.
Anonymous Object: an unnamed object.
Strategy Pattern: encapsulate an algorithm into a class by using anonymous classes & objects.
Comparable: natural sorting order, class needs to implement Comparable<T>
and override compareTo(T other)
.
Comparator: customer sorting order, need to instantiate an anonymous class and override compare(T objA, T objB)
.
Interface / Code Quality (4Cs)
1.) Cohesion (Single Responsibility per Class)
2.) Completeness or Convenience
3.) Clarity or Conciseness (a function should return an appropriate data type)
4.) Consistency (parameters naming)
Other Code Quality
5.) Constructor creates a fully formed object.
6.) Command-Query separation.
7.) Implement Iterable or Comparable.
8.) Breaking encapsulation.
Precondition: assumption at the start of the function.
Ex: when calculating a square root, the input must be non-negative integer.
Postcondition: conditions at the end of the function.
Ex: when calling sort function, the order of the items must be either ascending or descending order.
Invariants: conditions or porperties remain unchanged throughout the execution of the function.
Ex: in Queue data structure, the size of queue must not be negative.
Defensive Programming:
Programming by Contract:
Using Design by Contract and Defensive Programming:
Use Design by Contract
to communicate the expectations, and use Defensive Programming
to verify these expectations using asserts
and exceptions
.
Assertions
-ea
to Java Virtual Machine (JVM).
Polymorphism: late binding (runtime compilation) and provides loose coupling.
Ad-hoc Polymorphism: static binding by method overloading of specific type.
Parametric Polymorphism: static binding by using generic method that works for many data type.
Subtype Polymorphism: late binding (runtime) using inheritance or interface via method overriding.
Design Heuristic: code to an interface, not a concrete type.
@Override: tells the compiler to check if the overriding methods exist in the superclass
.
Promotion: casting lower-size data type to higher data type (int
to double
).
Demotion: casting higher-size data type to lower data type (double
to int
).
Java Garbage Collection: primitive data type stored in stack, objects are stored in heap.
Parsing String to Int: Integer.parseInt(userInput)
Operator Overloading: Java does not support operator overloading.
String: immutable, always create a new object when concatenating.
Comparable Example
public class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getID() {
return this.id;
}
public String getName() {
return this.name;
}
/**
* Natural ordering
*
* @param otherStudent
* @return
*/
public int compareTo(Student otherStudent) {
// Ascending order
// this.getName().compareTo(otherStudent.getName());
// Descending order
return otherStudent.getName().compareTo(this.name);
}
@Override
public String toString() {
return "ID: " + this.id + " NAME: " + this.name;
}
}
public static void main(String[] args) {
Student studentA = new Student(1, "Student A");
Student studentB = new Student(2, "Student B");
Student studentC = new Student(3, "Student C");
List<Student> students = new ArrayList<Student>();
students.add(studentA);
students.add(studentB);
students.add(studentC);
Collections.sort(students);
}
Comparator Example
public class Robot {
private int id;
private String name;
public Robot(int id, String name) {
this.id = id;
this.name = name;
}
public int getID() {
return this.id;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return "ID: " + this.id + " NAME: " + this.name;
}
}
public static void main(String[] args) {
Robot robotA = new Student(1, "Robot A");
Robot robotB = new Student(2, "Robot B");
Robot robotC = new Student(3, "Robot C");
List<Robot> robots = new ArrayList<Robot>();
robots.add(robotA);
robots.add(robotB);
robots.add(robotC);
Collections.sort(robots, (Robot robotA, Robot robotB) -> {
return robotA.getID() - robotB.getID();
});
}
Iterable and Iterator
public class Student {
private String name;
public Student (String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return "Student: " + this.name;
}
}
public class StudentsList implements Iterable<Student> {
private List<Student> students;
public StudentList() {
this.students = new ArrayList<>();
}
public void addStudent(Student newStudent) {
this.students.add(newStudent);
}
@Override
public Iterator<Student> iterator() {
return this.students.iterator();
}
}
public static void main(String[] args) {
StudentsList studentsList = new StudentsList();
studentsList.addStudent(new Student("Student A");
studentsList.addStudent(new Student("Student B");
studentsList.addStudent(new Student("Student C");
// Using iterable
for (Student student : studentsList) {
System.out.println(student);
}
}
super
refers to the superclass (automatically added if missing in the constructor).
this
refers to the current object.
A method cannot be both abstract
and final
.
abstract
methods are to be overridden while final
methods cannot be overridden.
A method cannot be both abstract
and static
.
abstract methods
are to be overridden by subclasses while static
methods belong to the class and cannot be overridden by the subclasses.
Cannot override private
, static
, or final
methods.
Shadow Variables: a subclass declares variables with the same name as the superclass. (do not do this).
Design Principle
Program to an interface, not implementation: allowing flexibility to reference a different concrete class later.
Prefer composition over inheritance: allowing referencing a new object, reduce rigid coupling from static inheritance hierarchy.
HTTP: hypertext Transfer Protocol.
HTTP Methods: GET
, POST
, PUT
, DELETE
.
API: application programming interface.
REST: representational state transfer.
TLA: three letter acronym.
CRUD: create, read, update, delete.
Springboot Framework: an dependency injection framework.
POJO: plain old java object.
Dependency Injection: allow loosely coupled classes, easy to mock object using POJO, pass objects as reference, no need to instantiate the object.
Path Endpoint Example
@GetMapping("/quotes/{id}")
public Quote getQuote(@PathVariable("id") long id) {
return QuoteRepository.find(id);
}
Query Endpoint (?key={someValue}
) Example
@GetMapping("/quotes/")
public Quote search(@RequestParam(value="search", defaultValue="") String searach) {
return QuoteRepository.search(search);
}
Body Endpoint (POST/PUT) Example
@PostMapping("/name")
public ResponseEntity<ProductDTO> updateProduct(@RequestBody ProductDTO product) {
ProductDTO updatedProduct = this.productService.updateProduct(productDTO);
return new ResponseEntity<>(updatedProduct, HttpStatus.OK);
}
super
in constructor and any overridden methods.protected
, and use private
except for protected
interface for the derived classes.subset
relationship with superclass.Liskov Substitution Principle (LSP)
B
can inherit A
iff for each method in A
, B
's method accepts all parameters, and does everything with those parameters.IS-A Limitation
Car
is a subtype of Vehicle
, then any code that expects Vehicle
should be able to work seamlessly with an instance of Car
.5 OOD Principles
SOLID:
S: Single Responsibility Principle (A class has one responsibility
)
O: Open Closed Principle (Be open for extension, closed for modification
)
L: Liskov Substitution Principle (Subtype objects interchangeable with base objects
)
I: Interface Segregation Principle (Prefer many client-specific interfaces
)
D: Dependency Inversion Principle (Depend on abstractions, not concrete classes
)
Self Use: base class calls a method which can be overridden by derived class.
Problem: when the base class calls its own methods which can be overridden.
Solution: use a private helper function that cannot be overridden.
Better Inheritance: use Polymorphism
Implementation Inheritance: inheriting from a concrete class to reuse its code.
Interface Inheritance: implementing an interface to support polymorphism.
Inheritance Guideline/Plan:
Iterator: no change, does not expose implementation details.
Software Design Pattern: allows discussion, implementation, and reuse of proven software designs.
MVC: separates model from view.
Facade Pattern: introduces a new class model to decouple UI from model complexity, and decouple UI from model complexity.
Observer Pattern
register
for updates with another object at runtime.one-to-many
relationship.subject
is the object being observed.observers
many object observing the subject.
Observer Example
// Subject: WeatherStation class
public interface WeatherStation {
void addObserver(WeatherObserver observer);
void removeObserver(WeatherObserver observer);
void notifyObservers();
}
public class WeatherStationImplementation implements WeatherStation {
private List<WeatherObserver> observers = new ArrayList<>();
private int temperature;
private int humidity;
public void addObserver(WeatherObserver observer) {
observers.add(observer);
}
public void removeObserver(WeatherObserver observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (WeatherObserver observer : observers) {
observer.update(this.temperature, this.humidity);
}
}
public void setMeasurements(int temperature, int humidity) {
this.temperature = temperature;
this.humidity = humidity;
notifyObservers();
}
}
// Observer: WeatherObserver interface
public interface WeatherObserver {
void update(int temperature, int humidity);
}
// Concrete observer: LCDDisplay
public class LCDDisplay implements WeatherObserver {
@Override
public void update(int temperature, int humidity) {
System.out.println("LCD Display: Temperature " + temperature + ", Humidity " + humidity);
}
}
// Concrete observer: SMSAlert
public class SMSAlert implements WeatherObserver {
private String phoneNumber;
public SMSAlert(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void update(int temperature, int humidity) {
if (temperature > 30 || humidity > 80) {
sendSMS(phoneNumber);
}
}
private void sendSMS(String phoneNumber) {
// Send SMS message.
}
}
public static void (String[] args) {
WeatherStation weatherStation = new WeatherStationImplementation();
LCDDisplay display = new LCDDisplay();
SMSAlert smsAlert = new SMSAlert("123456789");
weatherStation.addObserver(display);
weatherStation.addObserver(smsAlert);
}
Facade Design Pattern Example
// Concrete classes.
class Crust {
void makeCheeseCrust() { ... }
}
class Sauce {
void addTomatoSauce() { ... }
}
class Cheese {
void addMozarella() { ... }
}
class Toppings {
void addPepperoni() { ... }
void addMushrooms() { ... }
}
// Facade
public class PizzaOrderFacade {
private Crust crust;
private Sauce sauce;
private Cheese cheese;
private Toppings toppings;
public PizzaOrderFacade(Crust crust, Sauce sauce, Cheese cheese, Toppings toppings) {
this.crust = crust;
this.sauce = sauce;
this.cheese = cheese;
this.toppings = toppings;
}
public void orderPizza(String pizzaType) {
System.out.println("Ordering: " + pizzaType + " pizza.");
crust.makeCheeseCrust();
sauce.addTomatoSauce();
cheese.addMozzarella();
if (pizzaType.equals("Pepperoni")) {
toppings.addPepperoni();
} else if (pizzaType.equals("Veggie")) {
toppings.addMushrooms();
}
}
}
public static void main(String[] args) {
Crust crust = new Crust();
Sauce sauce = new Sauce();
Cheese cheese = new Cheese();
Toppings toppings = new Toppings();
PizzaOrderFacade pizzaOrder = new PizzaOrderFacade(crust, sauce, cheese, toppings);
pizzaOrder.orderPizza("Pepperoni");
}
private void methodA() {
try {
...
}
catch (TypeA exception) {
...
}
catch (TypeB exception) {
...
}
finally {
...
}
}
If no exception is thrown, finally
is called after try
.
If an exception is caught, finally
is called after the catch
.
If an exception is uncaught
finally
is called immediatelymain()
does not handle the exception, the program is terminated.Exception handling is a design decision.
Example: throw an exception in database to propagate up to UI to catch and display the error.
All exceptions inherit from Exception
class.
Possible methods: getMessage()
, printStackTrace()
2 Type of Exceptions
Checked Exception: must be either caught by the method or listed in methods throws exception.
// Checked exception: FileNotFoundException
private void actionA() throws FileNotFoundException {
...
}
// Checked exception: IOException
try {
FileReader fileReader = new FileReader("nonexistent.txt");
} catch (IOException e) {
// Handle exception.
}
Unchecked Exception: does not need to be caught or listed, RuntimeException
is unchecked.
// Unchecked exception: NullPointerException
String name = null;
System.out.println(name.length()); // throw NullPointerException
// Unchecked exception: ArithmeticException
int result = 10 / 0; // throw ArithmeticException, cannot divide a number by 0.
public class DemoCheckedExceptions {
public top() throws FileNotFoundException {
foo1();
}
void foo1() throws FileNotFoundException {
foo2();
}
void foo2() throws FileNotFoundException {
throw new FileNotFoundException()
}
}
Unchecked Exception
as Checked Exception
is required to change for all methods throwing and catching.Custom Exceptions
public class NoFileSelected extends RuntimeException {
public NoFileSelected() {
super();
}
public NoFileSelected(String message) {
super(message);
}
}
try-with-resources to automatically close files/scanners.
try (Scanner scanner = new Scanner(file)) {
if (scanner.hasNextInt()) {
int number = scanner.nextInt();
...
}
}
DRY: don’t repeat yourself, extract method, extract constant and pull-up to base class.
Template Method Design Pattern
compile time
) to behave like template.Strategy Pattern
runtime
), flexible for different algorithms during execution.Template Method Example
abstract class IntFileProcessor {
private int processFile(File file) {
try (FileReader reader = new FileReader(file)) {
Scanner scanner = new Scanner(reader);
int result = this.getStartValue();
while (scanner.hasNextInt()) {
result = this.processInt(result, scanner.nextInt());
}
return result;
} catch (IOException exception) {
execption.printStackTrace();
}
return 0;
}
abstract protected int getStartValue();
abstract protected int processInt(int current, int next);
}
class IntFileProcessorSum extends IntFileProcessor {
@Override
protected int getStartValue() {
return 0;
}
@Override
protected int processInt(int current, int next) {
return current + next;
}
}
class IntFileProcessorProduct extends IntFileProcessor {
@Override
protected int getStartValue() {
return 1;
}
@Override
protected int processInt(int current, int next) {
return current * next;
}
}
Object Equality: ==
compares two objects’ addresses.
Overriding equals(Object o)
class Car {
private String make;
private Date year;
private int seating;
private double weight;
@Override
public boolean equals(Object o) {
if (!(o instanceof Car) return false;
Car that = (Car) o;
return Objects.equals(this.make, that.make)
&& Objects.equal(this.year, that.year)
&& this.seating == that.seating
&& Double.compare(this.weight, that.weight) == 0;
}
@Override
public int hashCode() {
return Objects.hash(this.make, this.year, this.seating, this.weight);
}
}
equals()
Contract
x
and y
equals must be:x.equals(x)
must be true
.x.equals(y)
iff y.equals(x)
.x.equals(y) && y.equals(z) -> x.equals(z)
.x.equals(y)
remains unchanged for any invocations.x.equals(null)
must be false
.HashCode: Objects.hash()
must pass all fields that stores object’s states.
When overriding equals()
you must override hashCode()
whenever objectA.equal(objectB)
then objectA.hashCode() == objectB.hashCode()
; otherwise, collections may not work.
public interface Runnable {
void run();
}
class SomeTask implements Runnable {
@Override
public void run() {
... // do something.
}
}
public void main(String[] args) {
Runnable taskA = new SomeTask();
Thread threadA = new Thread(taskA);
threadA.start();
}
1.) Create a Task
that implements Runnable
.
2.) Create a Thread
that passes the Task
to it.
Timing
Time Slice: a block of time during which a Thread
can execute, OS/JVM allocates time-slices to threads.
Not always equals:
round-robin
(RR) scheduling.
Suspending a Thread
: Thread.sleep(delay)
Thread Synchronization
Race Condition: happens when multiple threads depends on shared resources and the order of threads are scheduled.
Cause/Issue:
Solution:
Using Locks
1.) Create a lock to access some resources.
2.) Lock the lock before accessing resources.
3.) Use resource.
4.) Unlock the lock when is done.
Deadlock: each thread is waiting for another one to release its lock, dining philosophers problem.
Dining philosophers problem: 2 chopsticks to eat but each philosopher sits side by side with each other.
Critical Section: a portion of thread’s execution where it can suffer a race condition.
Heisenbug: a bug whose behaviour is changed by looking for it, debugging can change thread timing resulting in changing behaviour.
Stopping a Thread
: normally threads end once run()
finished.
Runnable taskA = new TaskA();
Thread threadA = new Thread(taskA);
threadA.start()
// Do this after thread is no longer needed to safely stop a thread.
threadA.interrupt();