COMP2511 Notes

1. Object Oriented Introduction

1.1 Java

Java is a multiplatform development tool that is developed by Oracle. Some important features of the Java environment are as listed:

1.2 What is OO?

The object oriented paradigm is design to break up a large program into a series of classes that each have an individual responsbiility in that program. A class is similar to an abstract data type in that it has associated variables (properties) and functions (methods) that are defined and enclosed with the class declaration. The Java language is designed in such a way that everything is a class, and it comes with a massive host of library classes that can do pretty much everything. So to do something like printing to stdout, you would invoke the function like this:

public class HelloWorldProgram() {
    // Declaring a method (the main method)
    public static void main(String[] args) {
        System.out.println("Hello World!);
    }
}

In designing a class consider the following:

1.3 Inheritance

Inheritance is a form of software reusability in which new classes are created from the existing classes by absorbing their attributes and behaviours. So instead of having to define a completely separate new class, we can more easily define a subclass from a superclass. We can also further extend or modify our subclass if needed, hence normally subclasses have more features than their superclasses. Subclasses in Java can only extend one superclass at a time, this is called single inheritance, some languages allow for multiple inheritance.

Interfaces are a clean way to define purely abstract and undefined classes. They have no properties or method bodies. Abstract and concrete classes can implement more than one interface at a time, and is a way that Java gets around single inheritance. Interfaces can also be extended by sub-interfaces. A sub-interface inherits all the methods and constants of the super-interface. Interfaces can extend more than one interface at a time, allowing for multiple inheritance of interfaces.

public abstract class SuperClass {
    // This is the parent class
    // Abstract class cannot be instantioted
    public void doThing() { return 1 }
}

public class SubClass extends SuperClass {
    // This is the child class and inherits properties and methods from the parent
    // This can be instantiated
    @Override
    public void doThing() { return 2 }
}

public interface Foo {
    void fooBar() throws IOException;
}

public interface Bar {
    void fooBar() throws FileNotFoundException;
}

public class Main implements Bar, Foo {
    // The interface specified at the end is given higher precedence.
    public void fooBar() throws IOException;
}

A subclass can also be cast into the type of its superclass, and hence the subclass can pass an “is-a” test for the superclass.

1.4 Polymorphism

Polymorphism captures the ability of an object to take on different forms (types). Any object that can pass more than one “is-a” test is considered polymorphic. Technically all objects are polymorphic as every class implicitly extends the Object class, this is the only class without a superclass.

public interface Vegetable {}
public class Food {}

// Polymorphic since Broccoli is-a Food, is-a vegetable, is-a object.
public class Broccoli extends Food implements Vegetable {}

A subclass can also be cast into the type of its superclass, and hence the subclass can pass an “is-a” test for the superclass. This allows us to treat a family of subclasses as the parent class, while the subclasses still retain their individual behaviour. However we can only access methods that are specified or inherted from the superclass, access to the subclasses individual methods are restricted.

1.5 Generics

Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters prove

2. Object Oriented Design Principles

Building good software is all about:

2.1 Design Smells

A design smell is a symptom of poor design, and is often caused by violation of key design principles which makes the code harder to extend and easily maintain.

2.2 Coupling and Cohesion

The design quality of software is characterised by coupling and cohesion. Good software aims for loose coupling and high cohesion.

Coupling is defined as the degree of interdependence between components or classes, or the extend to which a class knows about other classes. High coupling occurs when a class A knows too much about another class B, and A depends to much on the B’s internal workings, and is hence affected by internal changes to B. This is not a maintainable system, and should be aimed to be more loosely coupled, where A depends on B’s external structure but not the internal workings. Zero coupled classes aren’t really useful, as classes need to interact with each other, so we should aim for loose coupling.

Cohesion is defined as the the degree to which all the elements of a component, class, or module work together as a functional unit. This is about making discrete parts of your structure have a unified purpose by separating out responsibility.

2.3 SOLID Principles

2.4 Design Principles

2.5 Code Smells

2.6 Refactoring Methods

3. Behavioural Design Patterns

3.1 Strategy Design Pattern

3.1.1 Problem

When we need a class to be able to have a method have multiple behaviours (and add behaviours later down the line). For example having a class Duck which has a method quack, however we want to extend this class to allow us to implement a RubberDuck which instead of using quack, we want it to sqeak. We can do this by creating a RubberDuck subclass that extends from duck and overrides the method, however if we to continue to add more duck subclasses and continue to override methods, it’s not effiecient.

public class Duck {
    private int age;
    private int colour;
    public void quack() {
        // Quacking behaviour
    }
}

public class RubberDuck extends Duck {
    @Override
    public void quack() {
        // Squeaking behaviour
    }
}

public class Mallard extends Duck {
    @Override
    public void quack() {
        // Yelling behaviour
    }
}

3.1.2 Solution

To avoid this issue with creating many subclasses that override the same functions we can instead encapsulate the differences into a separate class that controls the behaviour. This includes the added benefit of decoupling the behaviour from the user class, which allows the strategy method to be extendable without any modification to any code.

public class Duck {
    private int age;
    private int colour;
    private NoiseStrategy noise;
    public void makeNoise() {noise.makeNoise()}
}

public abstract class NoiseStrategy {
    public abstract void makeNoise();
}

public class Quack extends NoiseStrategy {
    public void makeNoise() {
        // Quacking behaviour
    }
}

public class Squeak extends NoiseStrategy {
    public void makeNoise() {
        // Squeaking behaviour
    }
}

public class Yell extends NoiseStrategy {
    public void makeNoise() {
        // Yelling behaviour
    }
}

3.2 State Pattern

3.2.1 Problem

When we have an object that needs to change its behaviour based on its current state. If we need to continue to add behaviours and states to the class, the class can become too large or have too many responsibilities, especially if the behaviours are unrelated. States can also be “easily” managed with switch-case statements (which are terrible terrible design), and we should refactor switch statements to state patterns.

// One class to manage all state logic in a switch statement
class CeilingFanPullChain {
    private int currentState;
    public void pull() {
        switch (this.currentState) {
            case 0:
                // low speed state logic
                this.currentState = 1
                break;
            case 1:
                // high speed state logic
                this.currentState = 2
                break;
            case 2:
                // off state logic
                this.currentState = 0
                break;
        }
    }
}

3.2.2 Solution

We can extract the behaviours and states into separate classes which can be better organised and prevent us from making large classes. The below example is too simplistic to actually be useful for the state pattern, however it demonstrates how each state is decoupled from the user class and from each other.

interface State {
    void pull(CelingFanPullChain wrapper);
}

// Wrapper class that delegates state logic to composed state objects
class CeilingFanPullChain {
    private State currentState;

    public void set_state (State newState) {
        this.currentState = newState;
    }

    public void pull() {
        currentState.pull(this);
    }
}

// Concrete state classses
class LowSpeedState implements State {
    public void pull(CeilingFanPullChain wrapper) {
        // low speed logic
        wrapper.setState(new HighSpeedState());
    }
}

class HighSpeedState implements State {
    public void pull(CeilingFanPullChain wrapper) {
        // high speed logic
        wrapper.setState(new OffState());
    }
}

class Offtate implements State {
    public void pull(CeilingFanPullChain wrapper) {
        // Off logic
        wrapper.setState(new LowSpeedState());
    }
}

3.3 Iterator Pattern

The iterator pattern allows for the traversal of items in a collection without revealing any internal data structures. Probably don’t have to worry too much about this one as both Java and C++ handle this one for us most of the time.

3.4 Template Method Patter

3.4.1 Problem

Basically if we wanna do something similar to C’s function pointer magic but in a more sensible and coherent manner. How can we switch out steps in the execution of a algorithm depending on what our needs.

3.4.2 Solution

You may have noticed by now, our solution to each problem is simply to add more complex class structures into the system, just assume that that’s the case for the rest of these, because my exam is tomorrow and I am struggling to catch up.