Skip to content

Week 2 - Fundamental OOP Principles

Fundamental Object Oriented Principles

The Beginning

Encapsulation

Definitions

Encapsulation: Group single responsibilities and make sure sensitive data is hidden from users.

Below are the important things to remember about encapsulation:

  • Private access modifiers should be used to protect instance variables you don’t wish to change outside the class
  • Getters and Setters should be used to protect how objects interact with sensitive data and variables.
  • Encapsulation provides access control like R, W, or RW
  • Flexible: Proper encapsulation means developers won’t inadvertently change encapsulated code while working on another piece of code that utilizes it.

Inheritance

Definitions

Inheritance: Share/inherit instance variables/attributes/fields and methods from superclass (parent) to subclass (child). The child inherits attributes and methods from the parent.

Below are the important things to remember about inheritance:

  • Every Java class extends the class Object
    • Which means that every class we create inherits all the methods defined in the Object class (e.g. equals, hashCode, …)
-note

Here are all the methods that a Java class inherits from the Object class, which is the ROOT object hierarchy:

  1. public final native Class<?> getClass(): Returns the runtime class of this Object.
  2. public native int hashCode(): Returns a hash code value for the object.
  3. public boolean equals(Object obj): Indicates whether some other object is “equal to” this one.
  4. protected native Object clone() throws CloneNotSupportedException: Creates and returns a copy of this object.
  5. public String toString(): Returns a string representation of the object.
  6. public final native void notify(): Wakes up a single thread that is waiting on this object’s monitor.
  7. public final native void notifyAll(): Wakes up all threads that are waiting on this object’s monitor.
  8. public final native void wait(long timeout) throws InterruptedException: Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
  9. public final void wait(long timeout, int nanos) throws InterruptedException: Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
  10. public final void wait() throws InterruptedException: Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
  11. protected void finalize() throws Throwable: Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
  12. public boolean equals(Object obj): Compares this object to the specified object. The default implementation is based on reference equality (==).
  • To inherit from a class, you must extend it (i.e. public class Engine extends Part).
  • Java can only inherit/extend a single parent (i.e. no multiple inheritance).
  • Only public and protected methods can be inherited.
    • So if a method should be private but inheritable, it should be protected
  • To change inherited methods, they must be overridden by defining a new implementation in the child/subclass
  • Mark a class final (i.e. final class SomeClass { ... }) if you don’t want it to be inherited.
  • Actual type of an object/instance dictates which method is executed (Show display from week 1 again).
  • If the class to be created is a special case of an existing class, inheritance is OK
  • If you notice that inheriting adds more responsibilities to a class, you should form multiple classes of the class. There should only be one reason for each class to change (Single Responsibility Principle).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Example parent/super-class
public class Superclass {

    private String objectVariable; // Not visible to child/subclass
    protected String objectName; // Visible to child/subclass, but not visible outside class.

    public Superclass() {
        this("Example");  // Example of calling its own overloaded contructor
    }

    public Superclass(String value) {
        this.objectVariable = value;
    }

    public String toString() {
        return this.objectVariable;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Example child/subclass
public class Subclass extends Superclass {

    public Subclass() {
        super("Subclass"); // Example of calling parent/super class constructor
    }

    @Override
    public String toString() {
        return super.toString() + "\n  from the child!";
    }
}

Abstraction

Definitions

Abstraction: The process of hiding certain details and showing only essential information to the user.

We’ve already talked about using methods to encapsulate and abstract algorithms, but abstraction in OOP is more broad, and can be accomplished with two different Java constructs: Abstract classes and Interfaces.

Abstraction should be used frequently in APIs and libraries to hide implementation details that would only create complexity for the consumers of the API or library or to achieve class security by hiding instance variables or methods that the API/library designers know changing would change or alter desired functionality.

Abstract Class

Definitions

Abstract class: A class that cannot be used to create objects because only some of the instance methods are implemented (i.e. not empty).

Below are the important things to remember about Abstract Classes:

  • They are defined by public abstract class …
  • Useful when there exists a clear concept, but that concept is not a good candidate for an object in itself.
  • They can contain normal implemented methods (usually methods common to concept).
  • Abstract methods are defined public abstract void methodName();
  • They must be inherited from another class, and the abstract methods (i.e. empty methods / method signatures) must be implemented to create objects.
  • Inheritance is used to implement Abstract classes.
  • Unlike Interfaces, Abstract Classes can contain Object/Instance variables and Constructors
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Example Abstract Class
public abstract class Operation {

    private String name;

    public Operation(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public abstract void execute(Scanner scanner);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Example Inheriting from Abstract Class
public class PlusOperation extends Operation {

    public PlusOperation() {
        super("PlusOperation");
    }

    @Override
    public void execute(Scanner scanner) {
        System.out.print("First number: ");
        int first = Integer.valueOf(scanner.nextLine());
        System.out.print("Second number: ");
        int second = Integer.valueOf(scanner.nextLine());

        System.out.println("The sum of the numbers is " + (first + second));
    }
}

Interface

Definitions

Interface: A completely abstract class, meaning all method bodies are empty (i.e. not implemented). Interfaces define behavior (i.e. actions, verbs, methods) that are required from a class without implementing those behaviors (i.e. methods).

Below are the important things to remember about Interfaces:

  • They’re defined the same way that regular Java classes are, but public interface … is used instead of public class …
  • An Interface Is a Contract of behavior (i.e. methods)
    • Interfaces define behavior through method names and their return values (AKA method signatures).
  • Interfaces are always abstract and public, so access modifiers are sometimes omitted.
  • Interface attributes/fields are by default public, static and final
  • An interface cannot contain a constructor because it cannot be used to create objects
  • Interfaces must be implemented (kinda like inherited), but uses implements instead of extends.
    • On implementation of an interface, you must override all of its methods.
    • You can implement multiple interfaces in one class.
1
2
3
4
// Example Interface Declaration
public interface Readable {
    String read();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Example Interface Implementation
public class TextMessage implements Readable {
    private String sender;
    private String content;

    public TextMessage(String sender, String content) {
        this.sender = sender;
        this.content = content;
    }

    public String getSender() {
        return this.sender;
    }

    public String read() {
        return this.content;
    }
}
 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
32
33
// Another Example Interface Implementation
public class Ebook implements Readable {
    private String name;
    private ArrayList<String> pages;
    private int pageNumber;

    public Ebook(String name, ArrayList<String> pages) {
        this.name = name;
        this.pages = pages;
        this.pageNumber = 0;
    }

    public String getName() {
        return this.name;
    }

    public int pages() {
        return this.pages.size();
    }

    public String read() {
        String page = this.pages.get(this.pageNumber);
        nextPage();
        return page;
    }

    private void nextPage() {
        this.pageNumber = this.pageNumber + 1;
        if(this.pageNumber % this.pages.size() == 0) {
            this.pageNumber = 0;
        }
    }
}

Polymorphism

Definitions

Polymorphism: The concept that many related classes can perform a single action in many different ways.

Regardless of the type of the variable, the method that is executed is always chosen based on the actual type of the object. Objects are polymorphic, which means that they can be used via many different variable types (i.e. Dog -> Animal -> Object). The executed method always relates to the actual type of the object. This phenomenon is called polymorphism.

Below are the important things to remember about Polymorphism:

  • Polymorphism arises through inheritance when multiple related classes share common actions/methods, but those methods do different things.
  • It is useful for code reuse of existing class methods and attributes.
  • Objects/instances can be represented through all of its actual types.
 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
class Animal {
    public void animalSound() {
        System.out.println("The animal makes a sound");
    }
}

class Pig extends Animal {
    public void animalSound() {
        System.out.println("The pig says: wee wee");
    }
}

class Dog extends Animal {
    public void animalSound() {
        System.out.println("The dog says: bow wow");
    }
}

class Main {
    public static void main(String[] args) {
        // Animal, Dog, and Pig are all treated the same
        Animal[] animals = {new Animal(), new Pig(), new Dog()};
        for( Animal animal : animals) {
            // But animalSound performs a different action
            animal.animalSound();
        }
    }
}

Example of Polymorphism: Actual Type Dictates Action

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.util.ArrayList;
class Main {
    public static void main(String[] args) {
        ArrayList<Point> points = new ArrayList<>();
        points.add(new Point(4, 8));
        points.add(new ColorPoint(1, 1, "green"));
        points.add(new ColorPoint(2, 5, "blue"));
        points.add(new Point3D(5, 2, 8));
        points.add(new Point(0, 0));

        for (Point p: points) {
            System.out.println(p);
        }
    }
}
 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
// Point type in 2D coordinate system
public class Point {

    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // Distance between two points if you can only travel in the direction of the coordinate axes.
    public int manhattanDistanceFromOrigin() {
        return Math.abs(x) + Math.abs(y);
    }

    protected String location(){
        return x + ", " + y;
    }

    @Override
    public String toString() {
        return "(" + this.location() + ") distance " + this.manhattanDistanceFromOrigin();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ColorPoint is identical to Point, but also has color
public class ColorPoint extends Point {

    private String color;

    public ColorPoint(int x, int y, String color) {
        super(x, y);
        this.color = color;
    }

    @Override
    public String toString() {
        return super.toString() + " color: " + color;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Point3D has no color but can be derived from a 2D Point
public class Point3D extends Point {

    private int z;

    public Point3D(int x, int y, int z) {
        super(x, y);
        this.z = z;
    }

    @Override
    protected String location() {
        return super.location() + ", " + z;
    }

    // Distance between two points if you can only travel in the direction of the coordinate axes.
    @Override
    public int manhattanDistanceFromOrigin() {
        return super.manhattanDistanceFromOrigin() + Math.abs(z);
    }
}
Walkthrough of Actual object Type Affecting Action (i.e. Polymorphism)
  1. A call of toString in the class Point3D does not exist, so the superclass is next to be examined.
  2. A call of toString in the superclass point is found, so the code inside the implementation of that method is executed
    • So the exact code to be executed is return "("+this.location()+") distance "+this.manhattanDistanceFromOrigin();
    • The method location is executed first
    • Look for a definition of location in the class Point3D. It can be found, so its code is executed.
    • This location calls the location of the superclass to calculate the result.
    • Next we look for a definition of manhattanDistanceFromOrigin in the Point3D class. It’s found and its code is then executed.
    • Again, the method calls the similarly named method of the superclass during its execution.