Archives Wild Code School
Java

Java Reskilling 02: Advanced Object-Oriented Programming Concepts: Inheritance

Quête 25 sur 43 · 4 images archivées

Avant cette quête, tu dois avoir terminé la quête suivante :

Quêtes liées

Concepts avancés de la programmation orientée objet : l'inheritance (héritage)

Le récit

Dans les quêtes sur la programmation orientée objet (POO), tu vas apprendre les concepts de l'OOP (POO), les bases de Java, le Java SDK et l'utilisation des bibliothèques standard de Java avec des exemples tirés du domaine de notre application exemple Tariff-Manager.

Dans cette quête sur l'inheritance, on va s'occuper des différents types de clients de notre application web Tariff-Manager.

👀 Introduction

Par définition, la programmation orientée objet te permet de penser ton code de façon abstraite, avec les états et les comportements concernés, au lieu de le voir simplement comme une suite d'instructions.

La classe Customer de l'exemple précédent avait des états (des attributs) appelés name, birthdate et active. Côté comportements, elle avait la méthode d'instance buy ainsi que la méthode statique sayWhatYouAre.

Et si on voulait créer une nouvelle classe appelée Employee ? Si on pense de façon abstraite, Customer et Employee ont des choses en commun, comme un nom et une date de naissance. On pourrait dire qu'ils sont tous les deux un User dans notre système. Un Customer est un User qui achète des produits dans notre entreprise, un Employee est un User qui travaille dans notre entreprise. Les deux partagent un nom et une date de naissance, mais ont aussi des attributs spécifiques différents comme lastPurchase et entryDate.

Le but final est de créer une classe Employee, mais comme beaucoup de ses états et comportements sont partagés avec Customer, comment éviter de dupliquer tout le code ?

C'est là que l'inheritance entre en jeu !

🤓 À la fin de cette quête, tu vas :

✅Connaître des composants avancés du langage Java : les classes abstraites (abstract classes), implements et extends ✅Comprendre des concepts avancés de la programmation orientée objet : les classes abstraites, l'inheritance, les relations is-a (est-un) ✅Être capable d'appliquer Java à des solutions métier avec des graphes de classes et d'objets en utilisant l'inheritance

Contenu de la quête

  • Les classes et méthodes abstraites
  • L'inheritance

👉 Les classes et méthodes abstraites

Les classes abstraites

Tu as la classe Customer, et tu veux la rendre plus générale en créant une classe abstraite User. Un customer aura la plupart des mêmes attributs qu'un user, comme un nom et une date de naissance.

Alors allons-y, créons la classe User :

java
public abstract class User {

    // Attributes  
    private String name;  
    private LocalDate birthdate;  

    // Constructor  
    public User(String name) {  
        this.name = name;  
    }  

    // getters and setters  
    public String getName() {  
        return this.name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

    public LocalDate getBirthdate() {  
        return this.birthdate;  
    }  

    public void setBirthdate(LocalDate birthdate) {
        this.birthdate = birthdate;
    }
}

Les plus observateurs d'entre vous ont peut-être remarqué le mot-clé abstract dans la déclaration de la classe.

En effet, un user n'est pas une entité précise : on peut imaginer nommer un customer sur un marché, mais un user est une entité plutôt abstraite. De la même façon, tu peux imaginer conduire une voiture d'une marque et d'un modèle précis, mais essayer d'imaginer conduire un « véhicule » en général, c'est plus difficile.

Info

Une classe abstraite ne peut pas avoir d'instance.

Il t'est donc impossible de créer un objet User.

Je te vois déjà là avec ton air perdu en disant : « Quoi, pas d'instance ?! ».

Reprenons notre exemple de la quête sur la programmation orientée objet : souviens-toi qu'en créant un nouvel objet avec la classe Customer, on crée une instance de cette classe.

java
Customer joe = new Customer("Joe", LocalDate.parse("06/09/1934"));

En initialisant ces attributs, un espace dans la mémoire de l'ordinateur est créé pour y sauvegarder notre customer. De plus, la référence joe pointe vers cet espace dans la mémoire.

Ce processus d'instanciation sera impossible avec une classe abstraite.

Mais à quoi sert une classe abstraite si elle ne peut pas être instanciée, me demandes-tu ?

Elle est là pour créer des comportements qui seront hérités par ses classes enfants.

Les méthodes abstraites

Info

Une méthode abstraite est une méthode qui n'a pas d'implémentation.

Tu as de nouveau cet air perdu... Ne pas avoir d'implémentation, ça veut dire que la méthode n'a pas de corps, seulement une signature.

Prends par exemple les oiseaux. Ils vont avoir un chant différent selon leur espèce : canard, aigle, corbeau, etc...

Quelle que soit l'espèce de l'oiseau, il aura un chant, mais tu ne peux pas prédéfinir lequel ce sera, à moins de savoir exactement de quelle espèce d'oiseau il s'agit.

Ici, dans notre classe User, on va définir une méthode abstraite doSomething :

java
public abstract void doSomething();

Comme tu peux le voir, il n'y a pas d'accolades pour définir le corps de cette méthode, parce que ce sont les classes qui vont hériter de Customer qui devront définir le comportement de la méthode.

Donc, User précise que tous les users spécifiques auront la capacité de doSomething(). Pour le Customer ça pourrait être afficher I am buying a CrazyGreenDress et pour un Employee ça pourrait être I am working on this new agile development project.

On a beaucoup parlé des classes héritées, sans expliquer ce qu'elles sont. Regardons-les de plus près dans la section suivante !

👉L'inheritance

Parent et enfants

Une fois qu'on a notre classe abstraite User, on va pouvoir l'utiliser comme classe parent et faire hériter notre nouvelle classe Customer.

Info

L'inheritance, du moins en programmation orientée objet, signifie qu'une classe enfant aura le droit d'accéder aux états et comportements de sa classe parent.

Par exemple, tu peux créer une classe abstraite Vehicle qui aura la marque et le kilométrage comme attributs. Tu peux ensuite créer des classes enfants Boat et Car qui vont en hériter.

Revenons à notre classe User. Imagine que tu crées une classe Customer qui hérite de la classe Bird, tu peux l'écrire en code comme ceci :

java
public class Customer extends User {  

}

Tu remarques peut-être le mot-clé extends. C'est le mot-clé qui gère l'inheritance.

Info

Une classe ne peut hériter que d'une seule autre classe.

En l'état, cette classe ne pourrait jamais passer la compilation pour deux raisons :

  1. Elle n'a pas de constructeur
  2. Elle n'implémente pas la méthode doSomething

Constructeur et super

Comme la classe Customer n'est pas abstraite, elle peut être instanciée. Pour ça, tu vas devoir préciser un constructeur. Cependant, elle hérite de User, qui a lui-même un constructeur, qui initialise name avec la valeur de l'argument.

Ce serait intéressant d'essayer d'appeler ce constructeur plutôt que d'en créer un de zéro. Voici comment tu vas faire :

java
public Customer(String name) {  
    super(name);  
}

Ce constructeur n'a pas le même contenu qu'un constructeur traditionnel : tu as peut-être remarqué l'utilisation du mot-clé super !

Info

super sert à appeler une méthode telle qu'elle est définie dans le parent.

Dans ce cas, il appelle le contenu du constructeur du parent, et instancie donc un objet Customer en associant la valeur de l'argument à son attribut name.

L'avantage de ça, c'est d'éviter une duplication inutile de code. Mais si Customer était totalement identique à la classe parent, il n'y aurait aucun intérêt à le créer.

En gardant ça en tête, on va maintenant voir comment étendre (extend) les états et comportements d'une classe héritée.

L'implémentation des méthodes abstraites

La méthode abstraite doSomething dans User attend d'être implémentée dans chaque classe héritée. Comme on l'a dit avant, chaque espèce d'oiseau aura un chant unique.

Pour répondre à cette attente, tu vas devoir inclure la méthode doSomething et son corps comme suit :

java
    @Override  
    public String doSomething() {  
        return "I am buying this CrazyGreenDress";  
    }
Info

Il n'est pas nécessaire d'utiliser @Override ici. C'est seulement là pour faire savoir au développeur que la méthode est héritée et modifiée.

Extends

La notion d'inheritance repose sur l'idée d'étendre la classe parent, d'où l'utilisation du mot-clé extends. Ça revient à pouvoir modifier ou ajouter des choses dans une classe enfant.

Prenons le cas de notre customer. Tu veux lui ajouter un attribut lastPurchase qui est réglé sur la date du dernier achat du customer, tout en conservant les attributs name et birthdate de User.

Tu vas pouvoir modifier la classe comme suit :

java
public class Customer extends User {  

    private LocalDate lastPurchase;

    public Customer(String name, LocalDate birthdate) {  
        super(name, birthdate);  
    }

    @Override
    public String doSomething() {
        return "I am buying!";
    }

    public LocalDate getLastPurchase() {  
        return lastPurchase;  
    }  

    public void setLastPurchase(LocalDate lastPurchase) {  
        this.lastPurchase = lastPurchase;  
    }  
}

Comme tu peux le voir ici, seul le nouvel attribut a été ajouté, et le constructeur a été modifié en conséquence pour initialiser l'attribut active après avoir appelé super, qui exécute le constructeur de User.

Tu peux modifier le constructeur de cette classe, par exemple, en initialisant l'attribut lastPurchase à null :

java
    public Customer(String name) {  
        super(name);  
        this.lastPurchase = null;  
    }
Info

Note que si l'attribut n'est pas défini, les types ont des valeurs par défaut. Pour les objets en Java, cette valeur par défaut est toujours null, pour un boolean c'est false, pour un int c'est 0.

🔬Exercice

Crée la classe Employee qui étend (extends) User :

  • pour un attribut entrydate qui est un LocalDate et donne l'information sur le début de l'emploi de cet Employee.
  • pour une méthode displayEmploymentInformation() qui retourne une String I am NAME and I am working here since ENTRYDATE où NAME est l'attribut name et ENTRYDATE est l'attribut entrydate.
hidden
See solution|||java|||Reveal class Employee with the method checkIfEmployed()|||0|||Hide solution
public class Employee extends User {

    private LocalDate entrydate;

    public Employee(String name, LocalDate birthdate) {
        super(name, birthdate);
        // if no entrydate is passed, it is assumed to be today
        this.entrydate = LocalDate.now();
    }

    public Employee(String name, LocalDate birthdate, LocalDate entrydate) {
        super(name, birthdate);
        this.entrydate = entrydate;
    }

    public String displayEmploymentInformation() {
        return "I am " + getName() + " and I am working here since " + entrydate;
    }

}

☝️Résumé

On a couvert beaucoup de concepts, tu sais maintenant comment :

  • Créer des classes abstraites
  • Créer des classes qui héritent de classes abstraites
  • Créer des instances de classes qui héritent d'une classe abstraite
  • Créer des références vers ces instances
  • Appeler des méthodes étendues depuis des classes héritées

🔬Plongée dans Tariff-Manager

↗
Application exemple Tariff Manager

Pour explorer davantage ces sujets, clone l'application exemple Tariff-Manager, bascule sur la branche block-2-complete, ouvre les classes du package dev.wcs.nad.tariffmanager.customer.model et regarde comment les classes sont organisées.

github.com

💪Challenge

Ce sera un challenge sous forme de Quizz.


Références

  • Photo de Daniel K Cheung sur Unsplash

Prior to this quest, you must have completed the following quest:

Quêtes liées

Advanced Object-Oriented Programming Concepts: Inheritance

Le récit

In the quests about Object-Oriented Programming (OOP) you will learn concepts of OOP, Java basics, the Java SDK, and using the Java Standard Libraries with examples from the domain of our sample application Tariff-Manager.

In this quest about inheritance, we are dealing with different types of customers of our Tariff-Manager Web application.

👀 Introduction

By definition, object-oriented programming allows you to think abstractly about your code and the relevant states and behaviours, as opposed to viewing it simply as a sequence of instructions.

The Customer class of the last example had states (attributes) called name, birthdate and active. As for behaviors, it had the instance method buy as well as the static method sayWhatYouAre.

What if we want to create a new class called Employee? If we think abstractly, Customer and Employee have some things is common, like a name and a birthdate. We could say they are both User in our system. A Customer is a User who buys products at our company, an Employee is a User who works in our company. Both share names and birthdates, but also have different specific attributes like lastPurchase and entryDate.

The ultimate goal is to create an Employee class, but since many of its states and behaviors are shared with Customer, how can we avoid duplicating all the code?

This is where inheritance comes in!

🤓 By the end of this quest, you will:

✅Know advanced components of the Java language: abstract classes, implements, and extends ✅Understand advanced concepts of object-oriented programming: abstract classes, inheritance, is-a relationships ✅Be able to apply Java for domain solutions with class and object graphs using inheritance

Content of the Quest

  • Abstract classes and methods
  • Inheritance

👉 Abstract classes and methods

Abstract classes

You have the Customer class, and you want to make it more general by creating an abtract User class. A customer will have most of the same attributes as a user, like having a name and a birthdate.

So let's go ahead and create the class User:

java
public abstract class User {

    // Attributes  
    private String name;  
    private LocalDate birthdate;  

    // Constructor  
    public User(String name) {  
        this.name = name;  
    }  

    // getters and setters  
    public String getName() {  
        return this.name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

    public LocalDate getBirthdate() {  
        return this.birthdate;  
    }  

    public void setBirthdate(LocalDate birthdate) {
        this.birthdate = birthdate;
    }
}

The eagle-eyed among you, may have noticed the keyword abstract in the class declaration.

Indeed, a user is not a specific entity: we can imagine naming a customer on a market, but a user quite an abstract entity. Similarly, you will be able to imagine driving a car of a specific make and model, but trying to imagine driving a "vehicle" in general, is more difficult.

Info

An abstract class cannot have an instance.

It is therefore impossible for you to create a User object.

I already see you standing there with a confused look saying: "What, no instance?!".

Let's take another look at our example from the quest on object-oriented programming: remember that by creating a new object with the Customer class we are creating an instance of this class.

java
Customer joe = new Customer("Joe", LocalDate.parse("06/09/1934"));

By initializing these attributes, a space in the computer’s memory is created to save our customer to. In addition, the reference joe points to this space in the memory.

This instantiation process will be impossible with an abstract class.

What is the point of having an abstract class if it can’t be instantiated, I hear you ask?

It’s there to create behaviours that will be inherited by its child classes.

Abstract methods

Info

An abstract method is a method that has no implementation.

You’ve got that confused face again... Not having an implementation means that the method does not have a body, only a signature.

Take for example birds. They will have a different song according to their species: duck, eagle, raven, etc....

Whatever the species of bird, it will have a song, but you can't pre-define which one it will be, unless you know exactly what species of bird it is.

Here, in our User class, we will define an abstract method doSomething:

java
public abstract void doSomething();

As you can see, there are no curly braces to define the body of this method, because it is the classes that will inherit from Customer that will have to define the behavior of the method.

So, the User specifies that all specific users will have the ability of doSomething(). For the Customer that might be printing I am buying a CrazyGreenDress and for an Employee that could be I am working on this new agile development project.

We've been talking about inherited classes a lot, without explaining what they are. Let's look take a closer look at them in this next section!

👉Inheritance

Parent and Children

Once we have our abstract User class, we will be able to use it as a parent class and have our new Customer class inherit it.

Info

Inheritance, at least in object-oriented programming, means that a child class will have the right to access the states and behaviours of its parent class.

For example, you can create an abstract class Vehicle that will have brand and mileage as attributes. You can then create Boat and Car child classes that will inherit from it.

Let's come back to our User class. Imagine that you create an Customer class that inherits from the Bird class, you can write that in code like this:

java
public class Customer extends User {  

}

You might notice the keyword extends. This is the keyword that manages the inheritance.

Info

A class can only inherit from one other class.

As such, this class would never be able to pass the compilation for two reasons:

  1. It has no constructor
  2. It does not implement the doSomething method

Constructor and super

Because the Customer class is not abstract, it can be instantiated. To do this, you’ll need to specify a constructor. However, it inherits User, which itself has a constructor, which initializes name with the argument value.

It would be interesting to try and call this constructor as opposed to creating one from scratch. Here's how you're going to do it:

java
public Customer(String name) {  
    super(name);  
}

This constructor does not have the same content as a traditional constructor: you may have noticed the use of the keyword super!

Info

super is used to call a method as defined in the parent.

In this case, it is calling the content of the parent's constructor, and therefore instantiating an object Customer and associating the value of the argument with its attribute name.

The advantage of this is to avoid unnecessary duplication of code. But if Customer were totally identical to the parent class, there would be no point in creating it.

Bearing that in mind, we will now cover how to extend the states and behaviours of an inherited class.

Implementation of abstract methods

The abstract method doSomething in User expects to be implemented within each inherited class. As we said before, every bird species will have a unique song.

To meet this expectation, you will need to include the doSomething method and its body as follows:

java
    @Override  
    public String doSomething() {  
        return "I am buying this CrazyGreenDress";  
    }
Info

It’s not necessary to use @Override here. It is only there to let the developer know that the method is inherited and modified.

Extends

The notion of inheritance is based on the idea of extending the parent class, hence the use of the keyword extends. This boils down to being able to modify or add things in a child class.

Let's take the case of our customer. You want to add a lastPurchase attribute to it that is set to the date of the last purchase by the customer, while also retaining the name and birthdate attributes of User.

You will be able to modify the class as follows:

java
public class Customer extends User {  

    private LocalDate lastPurchase;

    public Customer(String name, LocalDate birthdate) {  
        super(name, birthdate);  
    }

    @Override
    public String doSomething() {
        return "I am buying!";
    }

    public LocalDate getLastPurchase() {  
        return lastPurchase;  
    }  

    public void setLastPurchase(LocalDate lastPurchase) {  
        this.lastPurchase = lastPurchase;  
    }  
}

As you can see here, only the new attribute has been added, and the constructor has been modified accordingly to initialise the active attribute after calling super, which executes the User constructor.

You can modify the constructor of this class, for example, by initialising the attribute lastPurchase to null:

java
    public Customer(String name) {  
        super(name);  
        this.lastPurchase = null;  
    }
Info

Note that if the attribute is not set, types have default values. For objects in Java, this default value is always null, for boolean it is false, for int it is 0.

🔬Exercise

Create the class Employee which extends User:

  • for an attribute entrydate which is LocalDate and gives information about the start of the employment of this Employee.
  • for a method displayEmploymentInformation() which returns a String I am NAME and I am working here since ENTRYDATE where NAME is attribute name and ENTRYDATE is attribute entrydate.
hidden
See solution|||java|||Reveal class Employee with the method checkIfEmployed()|||0|||Hide solution
public class Employee extends User {

    private LocalDate entrydate;

    public Employee(String name, LocalDate birthdate) {
        super(name, birthdate);
        // if no entrydate is passed, it is assumed to be today
        this.entrydate = LocalDate.now();
    }

    public Employee(String name, LocalDate birthdate, LocalDate entrydate) {
        super(name, birthdate);
        this.entrydate = entrydate;
    }

    public String displayEmploymentInformation() {
        return "I am " + getName() + " and I am working here since " + entrydate;
    }

}

☝️Summary

We’ve covered a lot of concepts, you know now how to:

  • Create abstract classes
  • Create classes which inherit from abstract classes
  • Create instances of classes that are inheriting from an abstract class
  • Create references to these instances
  • Call extended methods from inherited classes

🔬Tariff-Manager Deep Dive

↗
Tariff Manager Sample Application

To examine these topics more, clone the sample application Tariff-Manager, switch to the branch block-2-complete, open the classes in package dev.wcs.nad.tariffmanager.customer.model and take a look at how the classes are organized.

github.com

💪Challenge

This will be a Quizz challenge.


References

  • Photo by Daniel K Cheung on Unsplash