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:
- It is always named after the class in which it's declared
- 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?
- Allows for code reusability
- Increases flexibility
- 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:
- Every time our car moves the tires lose their integrity. You decide the wear and tear rate;
- If a tire looses all its integrity, it should explode.
- If a tire explodes, the radio should turn on and play "Boom boom boom boom" by Vengaboys.
- 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:
- The ability to store a reference to a subtype instance in a supertype variable.
- 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:
- The necessary speed is higher than the maximum speed
- 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:
- The trip's speed is lower than 25km/h
- 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,840