Title Text

Title Text

Title Text

Introduction to programming

Object oriented programming

Object oriented paradigm

Paradigm: a model of something

Cambridge Dictionary

What is a Paradigm?

A paradigm is basically a way of doing things. A programming paradigm is, therefore, a way of solving problems using a programming language.

Object oriented paradigm

OOP is one of the most popular programming paradigms; most programmers will come into contact with it sooner or later.

 

OOP relies on the concept of classes and objects to structure software programs into simple, reusable pieces of code.

 

OOP is based on four major concepts (OOP pillars): Abstraction, Encapsulation, Inheritance, and Polymorphism.

 

Java is a heavily object-oriented language.

ADVANTAGES

OOP models complex, real-life entities as reproducible, simple structures.

 

These entities can be reused across programs.

 

Easier to debug and maintain.

What is an object?

How would you define a car?

It has a color, a brand, fuel level...

It accelerates, brakes, gets refuelled...

 

We could be here for a while, enumerating all the dos and don'ts of a vehicle, but bottom line is a car (and any object for that matter) can be defined as a set of properties (state) and behaviour.

NEXT

So, how do we create objects in Java?

classes And

object creation

What is a Class?

A  class is an abstract blueprint, a template, used to create more specific, concrete objects.

 

Usually, a class represents a broad category like Car, Person, Dog, etc.

class creation

class Car {

    // STATE
    String brand;
    String color;
    int fuelLevel;
}

Classes contain:

  • variables/attributes: their values may represent the current state
  • methods/functions: represent behavior and may change current state

 

So, how do we create an object?

Object creation

Steps to creating an object:

  • Declaration - Indicating variable type and name (Car car;)
  • Instantiation - Using the new keyword to create the object
  • Initialization - Using the constructor to initialize the object

 

Let's take a look at what happens in memory.

class Main {

    public static void main(String[] args){
    
    	// OBJECT DECLARATION/INSTANTIATION
        Car car = new Car();
        
        // STATE INITIALIZATION
        car.brand = "BMW";
        car.fuelLevel = 100;
    }
}

Object creation IN MEMORY

Object Behaviour

class Car {

    // STATE
    String brand;
    String color;
    int fuelLevel;
    
    // BEHAVIOUR
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
}

Putting Objects to work

class Main {

    public static void main(String[] args){
    
    	// OBJECT CREATION
        Car car = new Car();
        
        // STATE INITIALIZATION
        car.brand = "BMW";
        car.fuelLevel = 100;
        
        // METHOD CALLING
        car.accelerate();
    }
}

 

But now we have a colorless car wheeling around.

How can we solve this?

CONSTRUCTOR METHODS

What is a Constructor method?

A  constructor method is a special method, used to initialise our object's properties.

 

It is different from other methods because:

  1. It is always named after the class in which it's declared
  2. It doesn't have a return type

 

 

Let's take a look at our Car's constructor method.

default constructor

class Car {

    // STATE
    String brand;
    String color;
    int fuelLevel;
    
    // CONSTRUCTOR METHOD
    public Car(){
    
    }
    
    // BEHAVIOUR
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
}

The default constructor has no parameters, and we don't need to explicitly declare it. The compiler will add it for us, in compile-time.

constructor with parameters

class Car {

    // STATE
    String brand;
    String color;
    int fuelLevel;
    
    // CONSTRUCTOR METHOD
    public Car(String brand, String color){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = 100;
    }
    
    // BEHAVIOUR
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
}

this refers to the instance being accessed at that time.

It helps differentiate between the class variable and the constructor's parameter of the same name.

USING A constructor with parameters

class Main {

    public static void main(String[] args){
    
    	// OBJECT CREATION
        Car car = new Car("BMW", "red");
        Car car2 = new Car("Toyota", "grey");
        
        Car car3 = new Car(); // COMPILING ERROR
        Car car4 = new Car("Volvo"); // COMPILING ERROR
    }
}

Constructor overloadING

class Car {

    // STATE
    String brand;
    String color;
    int fuelLevel;
    
    // CONSTRUCTOR METHOD
    public Car(String brand, String color){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = 100;
    }
    
    // CONSTRUCTOR OVERLOADING
     public Car(String brand, String color, int fuelLevel){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = fuelLevel;
    }
    
    // BEHAVIOUR
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
}

METHOD overloadING

class Car {

  (...)
    public void getRefuelled(){
        fuelLevel = 100;
    }
    
    // METHOD OVERLOADING
    public void getRefuelled(int fuelLevel){
        this.fuelLevel += fuelLevel; // Let's pretend our cars have an infinite tank...
    }
    
}

Method overloading allows a class to have more than one method with the same name, as long as these methods have different signatures.

 

A method signature is composed of its name, number of arguments, as well as their order and type.

Live coding

Class And Object Creation

Blackjack Exercise

Blackjack is a popular casino game.

 

The main goal for the player is to achieve a hand whose points total nearer to 21 than the banker's (a.k.a dealer) hand, but without exceeding 21.

BlackJack rules

Let's create a (very) simplified version of this.

 

The idea is to have an application that "deals" cards until we reach a sum of card values equal to 21 or higher.

 

The cards that are "dealt" are created randomly: they can be repeated (as if we have an infinite deck).

 

At the end, the application should tell us whether we got "Blackjack!" or "Bust!"

Programming example

Some tips:

  • Create the Card class, using the ideas we used in the live coding
  • To simplify, let's consider the following values for the cards:
    • Ace = 1
    • Numbered cards have their number as value
    • Jack = 11, Queen = 12, King = 13
  • Create a variable that will be used to represent the sum of the values of the dealt car

Programming example

  • Create a loop, that represents the game, where a random card is created until we reach a sum of 21+
  • Print out the final result!

And don't forget your rubber ducky :)

Programming example

encapsulation

State access

Our blueprint is ready to rock; we can now create and use as many cars as we want.

 

However, the snippet above shows a situation that might induce a few errors. 

 

How can we solve this?

class Main {

    public static void main(String[] args){
    
    	Car car = new Car("BMW", "blue");
        
        car.brand = "Fiat";
    }
}

Encapsulation

class Car {

    // STATE
    private String brand;
    private String color;
    private int fuelLevel;
    
    // CONSTRUCTOR METHOD
    public Car(String brand, String color){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = 100;
    }
    
    // CONSTRUCTOR OVERLOADING
     public Car(String brand, String color, int fuelLevel){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = fuelLevel;
    }
    
    // BEHAVIOUR
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
}

Encapsulation

Encapsulation is an OOP pillar.

 

Objects data and the code that operates on that data is wrapped inside the object itself, hidden from other objects and classes.

class Main {

    public static void main(String[] args){
    
    	Car car = new Car("BMW", "blue");
        
        car.brand = "Fiat"; // COMPILING ERROR
        System.out.println(car.brand); // COMPILING ERROR
        
        // BUT WHAT IF I NEED TO READ THAT VALUE?
    }
}

READING DATA

class Car {

    // STATE
    private String brand;
    private String color;
    private int fuelLevel;
    
    (...)
    
    // GETTERS
    public String getBrand(){
        return this.brand;
    }
    
    public String getColor(){
        return this.color;
    }
}

Convention says that getter methods should always be named getPropertyName. If the property is a boolean, the method should be named isPropertyName.

WRITING DATA

class Car {

    // STATE
    private String brand;
    private String color;
    private int fuelLevel;
    
    (...)
    
    // SETTERS
   	public void setFuelLevel(int fuelLevel){
        this.fuelLevel = fuelLevel;
    }
}

Convention says that setter methods should always be named setPropertyName.

class members

INSTANCE VARIABLES

Variables name, hairColor, and dressColor are instance variables.

 

Each Doll object we create has its own copy of those variables.

 

One Doll object can't access the other Doll objects' values.

class Doll {

    private String name;
    private String hairColor;
    private String dressColor;
    
    public Doll (String name, String hairColor, String dressColor) {
    	this.name = name;
        this.hairColor = hairColor;
        this.dressColor = dressColor;
    }
}

Live codinG

DOLL COUNTER

CLASS VARIABLES

dollsCreated is a class variable.

 

Each Doll object has access to the same value, stored in the Doll class.

class Doll {

    private static int numberOfDollsCreated;
    
    private String name;
    private String hairColor;
    private String dressColor;
    
    public Doll (String name, String hairColor, String dressColor) {
    	this.name = name;
        this.hairColor = hairColor;
        this.dressColor = dressColor;
        
        numberOfDollsCreated++;
    }
}

CLASS VARIABLES IN MEMORY

CLASS METHODS

class Doll {

    private static int numberOfDollsCreated;
    
    private String name;
    private String hairColor;
    private String dressColor;
    
    public Doll (String name, String hairColor, String dressColor) {
    	this.name = name;
        this.hairColor = hairColor;
        this.dressColor = dressColor;
        
        numberOfDollsCreated++;
    }
    
    public static getNumberOfDollsCreated() {
    	return numberOfDollsCreated;
    }
}

EXERCISE

composition AND delegation

The car example

class Car {

    private String brand;
    private String color;
    private int fuelLevel;
    
    public Car(String brand, String color){
    	this.brand = brand;
        this.color = color;
        this.fuelLevel = 100;
    }
    
    public void accelerate(){
    	if(fuelLevel < 0){
    	    System.out.println("No vrum, vrum for you...");
            return;
        }
        
        fuelLevel--;
        System.out.println("Vrum, vrum!");
    }
    
    public void getRefuelled(){
        fuelLevel = 100;
    }
    
    public String getBrand(String brand){
        return this.brand;
    }
    
    public String getColor(String color){
        return this.color;
    }
}

If we start adding behaviour to our car, this class will start to get heavier and heavier. Also, what if we need to change the way our car accelarates?

The car example

So, what if we could separate our car into its individual parts?

This is what happens in real life.

A car as a whole is a sum of smaller parts.

 

How can we translate this into code?

The ENGINE

class Engine {

    private int range;
    private int fuelLevel;

    public Engine() {
        range = 10;
        fuelLevel = 100;
    }

    public int generateEnergy(int fuel) {
        if (fuelLevel - fuel < 0) {
            return 0;
        }
        fuelLevel -= fuel;
        return fuel * range;
    }

    public void setFuelLevel(int fuelLevel) {
        this.fuelLevel = fuelLevel;
    }
}

For every fuel liter, our engine generates energy to move our car for 10km. 

THE UPDATED CAR

class Car {

    private String brand;
    private String color;
    private Engine engine;

    public Car(String brand, String color, Engine engine){
        this.brand = brand;
        this.color = color;
        this.engine = engine;
    }

    public void accelerate(int fuel){
        int distance = engine.generateEnergy(fuel);
        
        if (distance > 0) {
            System.out.println("Vrum, vrum for " + distance + "km.");
            return;
        }

        System.out.println("No vrum, vrum for you!");
    }

    public void getRefuelled(){
        engine.setFuelLevel(100);
    }

    public String getBrand(String brand){
        return this.brand;
    }

    public String getColor(String color){
        return this.color;
    }
}

COmposition

Composition is a design technique to implement a has-a relationship in classes.

 

Car has an Engine.

Game has players.

 

Composition, although not considered an OOP pillar, is the most fundamental concept in this programming paradigm. It is achieved by using an instance variable that refers to another object.

WHY?

  1. Allows for code reusability
  2. Increases flexibility 
  3. Increases modularity

COMPOSITION AND DELEGATION

A design pattern where an object exposes its behaviour to the outside, and another object delegates the responsibility of performing a task to it.

EXERCISE

Add the tires and radio to our car skeleton.

 

Rules:

  1. Every time our car moves the tires lose their integrity.  You decide the wear and tear rate;
  2. If a tire looses all its integrity, it should explode.
  3. If a tire explodes, the radio should turn on and play "Boom boom boom boom" by Vengaboys.
  4. If a tire explodes, your car can't move again, until you change your tires.

EXERCISE

The Number Guessing Game

EXERCISE

The Restaurant

inheritance

the animals example

Let's outline three classes that would represent the three animals above.

the animals example

public class Dog {

    private int age;
    private int hungerLevel;
    private String name;
    private String breed;

    public Dog(String name, String breed) {
        this.name = name;
        this.breed = breed;
        hungerLevel = 0;
        age = 0;
    }

    public void makeNoise(){
        System.out.println("Woof, woof, woof.");
    }

    public void breathe(){
        System.out.println("Air in... Air out...");
    }

    public void eat (String food){
        if(hungerLevel <= 10){
            System.out.println("Thanks, but no thanks.");
            return;
        }
        System.out.println("Yum, yum, yum... This is some delicious " + food + "!");
    }

    public void getOlder(){
        age++;
    }
}
public class Elephant {

    private int age;
    private int hungerLevel;
    private String name;

    public Elephant(String name) {
        this.name = name;
        hungerLevel = 0;
        age = 0;
    }

    public void makeNoise(){
        System.out.println("The person creating this class has no idea of how an elephant sounds.");
    }

    public void breathe(){
        System.out.println("Air in... Air out...");
    }

    public void eat (String food){
        if(hungerLevel <= 10){
            System.out.println("Thanks, but no thanks.");
            return;
        }
        System.out.println("Yum, yum, yum... This is some delicious " + food + "!");
    }

    public void getOlder(){
        age++;
    }
}
public class Pig {

    private int age;
    private int hungerLevel;
    private String name;

    public Pig(String name) {
        this.name = name;
        hungerLevel = 0;
        age = 0;
    }

    public void makeNoise(){
        System.out.println("Oink, oink, oink.");
    }

    public void breathe(){
        System.out.println("Air in... Air out...");
    }

    public void eat (String food){
        if(hungerLevel <= 10){
            System.out.println("Thanks, but no thanks.");
            return;
        }
        System.out.println("Yum, yum, yum... This is some delicious " + food + "!");
    }

    public void getOlder(){
        age++;
    }
}

Looks like there's a lot of repetition here, right?

Is there a way to solve this?

the animals example

In reality, elephants, pigs and dogs all have one thing in common: they are all animals.

 

We can create what is known as a superclass (i.e., a more generic class), and have our more specific classes extend it.

INHEritance

Inheritance is an OOP pillar.

 

Inheritance defines a IS-A relationship between classes.

 

We can implement it using the keyword extends.

 

Inheritance supports the concept of code reusability.

WHEN TO USE INHEritance

Whenever there is a clear IS-A connection between classes.

 

If the classes share some behaviour, but they don't pass the IS-A test, then an inheritance relationship should not be implemented.

 

Example: A fish breathes and swims, so does a person. But a person is not a fish, and a fish is not a person.

SUPERCLASS ANIMAL

public class Animal {

    private int age;
    private int hungerLevel;
    private String name;

    public Animal(String name) {
        this.name = name;
        hungerLevel = 0;
        age = 0;
    }

    public void eat(String food) {
        if (hungerLevel <= 10) {
            System.out.println("Thanks, but no thanks.");
            return;
        }
        System.out.println("Yum, yum, yum... This is some delicious " + food + "!");
    }

    public void getOlder() {
        age++;
    }

    public void makeNoise(){

    }
}

SUBCLASSES

public class Elephant extends Animal {

    public Elephant(String name) {
        super(name);
    }

    @Override
    public void makeNoise() {
        System.out.println("The person creating this class has no idea of how an elephant sounds.");
    }
}
public class Pig extends Animal {

    public Pig(String name) {
        super(name);
    }

    @Override
    public void makeNoise() {
        System.out.println("Oink, oink, oink.");
    }
}

@Override annotation indicates that the current subclass is overwriting the method of its base class.

SUBCLASSES

public class Dog extends Animal {

    private String breed;

    public Dog(String name, String breed) {
        super(name);
        this.breed = breed;
    }

    @Override
    public void makeNoise() {
        System.out.println("Woof, woof, woof.");
    }

    public void runAfterMailman(){
        System.out.println("I HATE YOU! WOOFF! GET OUT OF MY STREET! WOOF!");
    }
}

Subtype specific behaviour and characteristics can be implemented in the subclass.

Creating AND USING THE objects

public class Main {

    public static void main(String[] args) {
        Pig pig = new Pig("Peppa");
        Elephant elephant = new Elephant("Tobias");
        Dog dog = new Dog("Gucci", "chihuahua");

        pig.makeNoise();
        elephant.makeNoise();
        dog.makeNoise();
        
        // THE eat() METHOD IS DECLARED IN THE SUPERCLASS, BUT EACH SUBTYPE INHERITED ITS IMPLEMENTATION
        pig.eat("potatoes");
        elephant.eat("leaves");
        dog.eat("biscuit");
    }
}

Creating AND USING THE objects

public class Main {

    public static void main(String[] args) {
    
        // SAVING THE REFERENCE TO OUR SUBTYPE INSTANCES IN A SUPERTYPE VARIABLE
        Animal pig = new Pig("Peppa");
        Animal elephant = new Elephant("Tobias");
        Animal dog = new Dog("Gucci", "chihuahua");

        pig.makeNoise();
        elephant.makeNoise();
        dog.makeNoise();

        // THE eat() METHOD IS DECLARED IN THE SUPERCLASS, BUT EACH SUBTYPE INHERITED ITS IMPLEMENTATION
        pig.eat("potatoes");
        elephant.eat("leaves");
        dog.eat("biscuit");
    }
}

WHAT ABOUT THE DOG?

public class Main {

    public static void main(String[] args) {
        Animal dog = new Dog("Gucci", "chihuahua");

        dog.makeNoise();
        dog.eat("biscuit");
        dog.runAfterMailman(); // COMPILATION ERROR
    }
}

Why?

WHAT ABOUT THE DOG?

public class Main {

    public static void main(String[] args) {
        Animal dog = new Dog("Gucci", "chihuahua");

        dog.makeNoise();
        dog.eat("biscuit");
        ((Dog) dog).runAfterMailman(); // OK!
    }
}

Type casting: converting a data type into a different type.

In this case, we needed to manually cast the dog object to access the methods for Dog class.

BEAR SHOP EXERCISE

The simple bear

Like all bears, it can talk.

 

Every time it talks, it says "I love you".

 

When its batteries are exhausted, it loses its ability to talk.

The CRANKY bear

Like all bears, it can talk.

 

Every time it talks, it says "I love you".

 

When its batteries reach a lower than 50% energy level, it loses its ability to talk. However, it can still sing a sad song one time, if it hasn't before.

the drunk bear

Like all bears, it can talk.

 

Every time it talks, it says "I love you", but it has a 0.2% probability of falling asleep. When this happens, this bear loses its ability to talk, snoring instead.

the SHOP

The shop creates one bear at the time, and never stores them.

 

The created bears follow a pattern:

  • If the number of bears created is even, it creates a Simple Bear.
  • If the number of bears created is a multiple of 5, it creates a Cranky Bear.
  • If it's neither, it creates a Drunk Bear.

 

The shop can compare itself to another shop. The store with more bears created, wins.

abstract classes

Drawing EXERCISE

Every student has exactly one minute to draw an animal.

 

When the minute is over, you'll show your masterpiece to the rest of the class.

P.s. It will make sense, eventually.

Drawing EXERCISE

An animal is an eukaryotic, multicellular living being that belongs to the Animalia biological kingdom. All animals share some characteristics/behaviours.

 

However, the animal itself isn't a concrete being; the specific subtypes are.

 

 

Can you relate this experience to any of our previous coding exercises?

The BEAR Class

public class Bear {

    private int energy;
    private BearType type;
    private boolean talkingMode;

    public Bear(BearType type) {
        energy = 100;
        talkingMode = true;
        this.type = type;
    }

    public void talk() {
        (...)
    }

    public void loseEnergy() {
        (...)
    }

    public boolean canTalk() {
        (...)
    }

    public void turnOnTalkingMode() {
        (...)
    }

    public void turnOffTalkingMode() {
        (...)
    }

    public int getEnergy() {
        (...)
    }

    private void checkEnergyLevel() {
        (...)
    }

    @Override
    public String toString() {
        (...)
    }

}

The BEAR Class

public class Main {
    public static void main(String[] args) {
        Bear bear = new Bear();
    }
}

There's not one single situation where it would make sense to write the code above. Not in the bear shop exercise context.

 

Our Bear is a conceptual entity, that exists solely for the purpose of establishing an inheritance relationship between our classes.

Abstract classes

public abstract class Bear {

    private int energy;
    private BearType type;
    private boolean talkingMode;

    public Bear(BearType type) {
        energy = 100;
        talkingMode = true;
        this.type = type;
    }

    public void talk() {
        (...)
    }

    public void loseEnergy() {
        (...)
    }

    public boolean canTalk() {
        (...)
    }

    public void turnOnTalkingMode() {
        (...)
    }

    public void turnOffTalkingMode() {
        (...)
    }

    public int getEnergy() {
        (...)
    }

    private void checkEnergyLevel() {
        (...)
    }

    @Override
    public String toString() {
        (...)
    }

}

The Java language predicts situations like this, which brings us to the next topic: Abstract Classes.

Abstract classes

public class Main {
    public static void main(String[] args) {
        Bear bear = new Bear(); // COMPILING ERROR
    }
}

An abstract class defines a conceptual type that can't be instantiated, but can be extended.

public class SimpleBear extends Bear {

    public SimpleBear() {
        super(BearType.SIMPLE_BEAR);
    }
}

Abstract Methods

Abstract classes may have abstract and concrete methods. Abstract methods are methods that don't contain any implementation.

public abstract class Bear {

    private int energy;
    private BearType type;
    private boolean talkingMode;

    public Bear(BearType type) {
        energy = 100;
        talkingMode = true;
        this.type = type;
    }

    // IF ALL SUBTYPES HAD A DIFFERENT WAY OF TALKING, 
    // THE talk() METHOD COULD BE ABSTRACT
    public abstract void talk();
    
    (...)
}

OBJECT CLASS

public class Main {
    public static void main(String[] args) {
        CrankyBear cranky = new CrankyBear();
        cranky.talk();
        System.out.println(cranky.toString());
    }
}

Method inheritance

How does the CrankyBear object have access to the talk() method?

 

How does the CrankyBear object have access to the toString() method?

The Object class

In Java, if a class isn't explicitly extending another class, then it is implicitly extending the Object class.

 

In the Bear Shop Exercise, CrankyBear, SimpleBear, and DrunkBear explicitly extend Bear. Bear implicitly extends Object. 

 

Bear inherits the toString() method from Object. Other methods, like the equals() method, are also inherited from Object.

The Object class

public class Main {
    public static void main(String[] args) {
        Object bear = new CrankyBear();
        bear.talk(); // COMPILING ERROR
        System.out.println(vampire.toString());
    }
}

Exercise

The Bank

enums

THE COFFEE SHOP EXAMPLE

public class CoffeeShop {

    public Drink serveDrink(String drink) {

        switch (drink) {
            case "macchiato":
                return new Drink("coffee, milk");
            case "mocha":
                return new Drink("coffee, chocolate syrup, milk");
            case "caramel frappuccino":
                return new Drink("coffee, milk, ice, caramel syrup");
            default:
                return new Drink("water");
        }
    }
}

This is a peculiar coffee shop, isn't it?

If a customer asks for a cappuccino and gets handed a glass of water, they won't be coming here again. 

Menu

public class DrinkType {

    public static final DrinkType WATER = new DrinkType(1, "water");
    public static final DrinkType MACCHIATO = new DrinkType(2, "coffee, milk");
    public static final DrinkType MOCHA = new DrinkType(3, "coffee, chocolate syrup, milk");
    public static final DrinkType CARAMEL_FRAPPUCCINO = new DrinkType(4, "coffee, milk, ice, caramel syrup");

    private int id;
    private String ingredients;

    private DrinkType(int id, String ingredients){
        this.id = id;
        this.ingredients = ingredients;
    }

    public int getId() {
        return this.id;
    }
}

What if we created a menu? That way everyone would know what to order.

UPdated coffee shop

public class CoffeeShop {

    public Drink serveDrink(DrinkType drink) {

        switch (drink.getId()) {
            case 1:
                return new Drink(DrinkType.WATER);
            case 2:
                return new Drink(DrinkType.MACCHIATO);
            case 3:
                return new Drink(DrinkType.MOCHA);
            case 4:
                return new Drink(DrinkType.CARAMEL_FRAPPUCCINO);
            default:
                return new Drink(DrinkType.WATER);
        }
    }
}

Now the program itself prevents the customer from ordering any type of drink that isn't on the menu.

ENUM

The Java language provides an easier, cleaner way for us to do what we just did. It's called an enum.

 

An enum is a special kind of class whose instances are finite and represented as constant variables of the class itself.

 

To create an enum, we use the keyword enum.

public enum DrinkType {
    WATER,
    MACCHIATO,
    MOCHA,
    CARAMEL_FRAPPUCCINO
}

ENUM

Enum constructor methods are private by default. The idea is to prevent the creation of extra instances.

 

Enums are still just classes, so they can have extra methods and properties.

public enum DrinkType {
    WATER(1, "water"),
    MACCHIATO(2, "coffee, milk"),
    MOCHA(3, "coffee, chocolate syrup, milk"),
    CARAMEL_FRAPPUCCINO(4, "coffee, milk, ice, caramel syrup");

    private int id;
    private String ingredients;

    private DrinkType(int id, String ingredients){
        this.id = id;
        this.ingredients = ingredients;
    }

    public int getId() {
        return this.id;
    }
}

UPDATED COFFEE SHOP

public class CoffeeShop {

    public Drink serveDrink(DrinkType drink) {

        switch (drink) {
            case MOCHA:
                return new Drink(DrinkType.MOCHA.getIngredients());
            case MACCHIATO:
                return new Drink(DrinkType.MACCHIATO.getIngredients());
            case CARAMEL_FRAPPUCCINO:
                return new Drink(DrinkType.CARAMEL_FRAPPUCCINO.getIngredients());
            default:
                return new Drink(DrinkType.WATER.getIngredients());
        }
    }
}

Iterating over an enum

public class CoffeeShop {

    public void askForMenu() {
        DrinkType[] drinks = DrinkType.values();

        for (DrinkType drinkType : drinks) {
            System.out.println(drinkType);
        }
    }
    
    (...)
}

EXERCISE

Let's remake our Rock-Paper-Scissors exercise, now using enums.

polymorphism

Polymorphism

Objects from different types can be accessed through the same interface (supertype), and still behave according to their specific implementations.

Polymorphism

Polymorphism is an OOP pillar.

It refers to the objects' ability to take many forms.

 

It comprises two concepts:

  1. The ability to store a reference to a subtype instance in a supertype variable.
  2. The ability to postpone to runtime the decision of which method implementation to invoke.

Polymorphism

We've seen it happen before:

 

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog("Gucci", "chihuahua");

        dog.makeNoise();
        dog.eat("biscuit");
        ((Dog) dog).runAfterMailman(); // OK!
    }
}

Polymorphic objects can be referred to using their type or their super types.

Instanceof

In some cases, it might be important to know if an object is of a specific type (which could be a class or subclass).

The instanceof keyword can be used to perform this check:

public class Main {

    public static void main(String[] args) {
        Animal dog = new Dog("Gucci", "chihuahua");
        
        System.out.println(dog instanceof Animal); //true
        System.out.println(dog instanceof Dog); //true
        System.out.println(dog instanceof Elephant); //false
        
        if(dog instanceof Dog){
        	((Dog) dog).runAfterMailman();
            //This way we make sure the object is of the correct class
        }
    }
}

"object instanceof ClassName" returns true if the object is of the provided type

EXERCISE

The Vehicle Rental Shop

A vehicle rental shop has a list of vehicles available for renting.

These vehicles can be rented by requesting a car or a motorcycle, specifically.

All vehicles start with 25L in their tank.

 

After being used, the vehicle can be returned to the store.

The vehicle should be returned in specific conditions:

The fuel amount available in the vehicle should be 20L. If not, the missing amount will be charged (1.5 euro/L).

Vehicle: Car

A car is a vehicle, and it as all vehicles, it has a model name and gas consumption value (KM/L). It also has a maximum speed value of 120km/h.

 

A trip with the car (driving) is represented by indicating the distance and time of the trip.

The trip cannot be performed if:

  1. The necessary speed is higher than the maximum speed
  2. There is not enough gas for the distance

Otherwise, the trip will result in reducing the available fuel (according to its gas consumption value).

The car, as any vehicle, can be refuelled.

Vehicle: hybrid Car

A hybrid car is a specific type of car, which has a battery-related properties beyond the car properties.

Specifically, hybrid cars have a battery power (starting at full charge), and a battery consumption value (KM/%).

 

For driving, the hybrid car's system may choose to spend battery power for the trip if:

  1. The trip's speed is lower than 25km/h
  2. There is enough battery power for the trip

Otherwise, it behaves as a regular car.

The battery of the hybrid car can also be recharged.

Vehicle: motorcycle

A motorcycle is a vehicle, and it has a model name, a gas consumption value (KM/L), and a maximum speed value of 100km/h.

 

The trip is performed similarly to a car, with a small difference: motorcycles can only perform shorter trips, with a maximum of 80km.

 

The motorcycle, as any vehicle, can be refuelled.

Pillars of oop

Abstraction

The process of hiding certain details that aren't essential for the representation of information that is useful for the user. It's a way of handling complexity.

 

Do I need to know how the coffee machine works on the inside in order to make a coffee? No.

encapsulation

The mechanism of hiding information (data and functionality) inside the object itself, without giving access to other objects/classes.

 

Encapsulation and abstraction are deeply correlated concepts.

inheritance

Inheritance is the process of deriving a class from another class, making the derived class share the attributes and functionality of the other.

 

In Java, it defines an IS-A relationship between classes.

polymorphism

Objects from different types can be accessed through the same interface (supertype), and still behave according to their specific implementations.

THE MONSTER EXERCISE

Game

Two players can play this game.

 

When the game starts, each player will be asked to pick a number of monsters to play with. Both players have to pick the same number of monsters.

 

In every round, the attacker player picks one monster to perform the attack. The other player picks one of their monster to suffer the hit. Both picks are random.

 

The game goes on until one of the players loses all their monsters.

Player

A  player has a predefined number of monsters to play with.

 

Every time a player is attacked, they should check if there's still any monster left alive to play with. When there's no more monsters, the player loses the game.

 

 

WEREWOLF

Werewolf is a type of monster.

 

When a Werewolf attacks, it always inflicts the same amount of damage.

 

When a Werewolf is hit, it always loses health.

VAmpire

Vampire is a type of monster.

 

When a Vampire attacks, it always inflicts the same amount of damage. However, there is a chance that, when it attacks, the vampire bites its enemy. When that happens, the vampire's health will increase a certain amount.

 

When a vampire is hit, it always loses health.

Mummy

Mummy is a type of monster.

 

A Mummy can only perform two consecutive attacks. If a Mummy tries to attack a third time, it fails, unrolls, and loses a certain amount of health. The attacking ability of the Mummy is then restored.

 

When a Mummy is hit, it always loses health.

Class Diagram

THE MONSTER EXERCISE

(OBSTACLE EDITION)

Game Obstacles

Let's take our Monster Game to the next level, and introduce a few game obstacles. 

 

These obstacles will be generated by the Game itself, randomly.

 

When the Game generates an obstacle, the players won't attack each other. Instead, they will either attack or be attacked by the generated game obstacle.

Fairy

The Fairy is a Supernatural being that attacks exactly like a Monster. However, it is not a monster.

 

The Fairy can't be attacked by any monster, so it never loses health.

 

It's a fairy.

Would you attack a fairy?

WITCH

The Witch is a Supernatural being that attacks like a monster, but only suffers half of the damage caused by the enemy.

The Supernatural superclass

Because Fairies and Witches attack like Monsters, it would be wise to create a common ancestral.

public abstract class Supernatural {

    private int hitPower;
    private int health;

    public Supernatural(int hitPower) {
        this.hitPower = hitPower;
        this.health = 100;
    }

    public int attack() {
        System.out.println(this + " is attacking!");
        return hitPower;
    }

    public int getHealth() {
        return health;
    }

    public int getHitPower() {
        return hitPower;
    }

    public void setHealth(int health) {
        this.health = health;
    }
}

The Supernatural subclasses

public abstract class Monster extends Supernatural {

    private MonsterType type;
    private boolean dead;

    public Monster(int hitPower) {
        super(hitPower);
    }

    (...)
}
public class Fairy extends Supernatural {

    public Fairy(int hitPower) {
        super(hitPower);
    }
}
public class Witch extends Supernatural {

    public Witch(int hitPower) {
        super(hitPower);
    }
}

NON-ATTACKABLE SUPERNATURALS

When it's time to attack, how can we differentiate between the supernaturals that can be attacked and those that can't?

 

Can we create another superclass for attackable supernaturals?

public abstract class Attackable {
    public abstract void suffer(int amount);
}
public abstract class Monster extends Supernatural, Attackable {

    private MonsterType type;
    private boolean dead;

    public Monster(int hitPower) {
        super(hitPower);
    }

    public void suffer(int damage) {
        (...)
    }
}

MULTIPLE INHERITANCE IN JAVA

There is no multiple inheritance in Java.

NON-ATTACKABLE SUPERNATURALS

What if there was a way to create a new, completely abstract type, that defined a contract that our classes could implement freely, regardless of their inheritance chain?

 

That concept exists; it is called an Interface!

interfaces

THE SHAPES EXAMPLE

THE SHAPES EXAMPLE

THE SHAPES EXAMPLE

public interface Drawable {
    void draw();
}
public interface Fillable {
    void fill();
}
public abstract class GeometricShape implements Fillable, Drawable {

    private int area;

    public GeometricShape(int area) {
        this.area = area;
    }

    @Override
    public void fill() {
        // fill drawn area
    }
}

EXERCISE - the game obstacles

Object Oriented Programming

By Soraia Veríssimo

Object Oriented Programming

  • 1,850