Semestre 2
Modules
Programmation
POO

Programmation - POO

En programmation procédurale, les méthodes sont les entités de base utilisées pour diviser un programme en parties plus petites. Ce paradigme vise à réutiliser le code en utilisant des méthodes. Cependant, il peut être difficile d'implémenter des scénarios complexes du monde réel uniquement avec ce paradigme.

Qu'est-ce que la programmation orientée objet ?

La programmation orientée objet (POO) est un paradigme de programmation basé sur les concepts de classes et d'objets.

Les classes et les objets sont les éléments fondamentaux de la POO.

La POO permet de modéliser des scénarios du monde réel en utilisant des langages de programmation.

L'objectif principal de la POO est de diviser un programme complexe en différents objets qui interagissent entre eux.

Dans la POO, les objets du programme représentent souvent des objets réels du monde réel.

poo.jpg

Dans une application, il existe de nombreux objets qui ne correspondent pas directement à des entités du monde réel. Ces objets sont utilisés pour gérer des fonctionnalités spécifiques de l'application telles que l'authentification, la création de modèles, la gestion des requêtes, et bien d'autres encore. Ils jouent un rôle essentiel dans le bon fonctionnement de l'application.

Anatomie des objets et des classes

Les objets sont composés de champs (variables) qui contiennent des données et de méthodes qui opèrent sur ces données. Les objets du monde réel ont des caractéristiques propres, par exemple, une ampoule peut être allumée ou éteinte pour émettre ou arrêter de la lumière.

Ces objets sont créés à partir de classes. Une classe peut être considérée comme un modèle ou un plan pour créer des objets similaires. Elle définit les attributs et les comportements communs à tous les objets de cette classe. light1.jpg light2.jpg light3.jpg

De l'illustration ci-dessus, on peut retenir que les objets ont un état qui est généralement représenté par des variables dans une classe, et qu'ils ont également un comportement qui est modélisé en implémentant des méthodes. 2bo.png

"On peut déduire de la discussion ci-dessus que les classes sont des types de données définis par l'utilisateur mis en œuvre à l'aide des types de données primitifs, par exemple boolean, int, charetc."

Java

Java est un langage de programmation populaire utilisé depuis plus de 20 ans. Sa philosophie "compiler une fois, exécuter n'importe où" est l'un de ses principaux avantages. Vous pouvez écrire du code Java sur n'importe quel appareil, le compiler en code machine, puis l'exécuter sur différentes plateformes grâce à la machine virtuelle Java ( JVM). Cela garantit une grande compatibilité avec plusieurs plates-formes.

En Java, chaque programme est écrit dans une classe. La classe principale est généralement appelée la "Main class". C'est à l'intérieur de cette classe que le code principal du programme est défini et exécuté.

Classes et objets

Une brève rencontre

Dans le monde réel, les objets tels que les voitures, les bâtiments et les humains ont tous un état et un comportement. Par exemple, une voiture peut avoir des états tels que le nom, la vitesse et le niveau de carburant, et elle peut avoir des comportements tels que faire le plein, conduire et se garer. Ces caractéristiques d'état et de comportement définissent les objets et permettent de les manipuler dans un programme. voiture.png

Définition

Les classes sont les éléments fondamentaux des programmes construits selon la méthodologie orientée objet. Elles permettent de définir de nouveaux types de données personnalisés.

En Java, nous disposons de différents types de données prédéfinis tels que int, char, boolean, etc.

Un objet est une instance d'une classe, et la classe en est la définition.

L'utilisation des types de données prédéfinis offre des fonctionnalités limitées. C'est pourquoi nous pouvons créer nos propres objets en utilisant les classes.

Les classes nous permettent de créer des types de données personnalisés. Les types de données prédéfinis en Java sont eux-mêmes des classes. Nous pouvons utiliser ces types de données de base pour créer nos propres classes. Une classe peut contenir plusieurs variables, pointeurs et fonctions qui seront disponibles chaque fois qu'un objet de cette classe sera créé. diagclasse.png

Dans la classe "Car" mentionnée ci-dessus, nous pouvons observer deux types d'attributs. Ces deux catégories d'attributs sont généralement présentes dans toutes les classes.

Champs

Les variables membres, également appelées variables d'instance, sont utilisées pour stocker les informations spécifiques à un objet d'une classe. Par exemple, un objet de type "voiture" pourrait avoir des variables membres telles que " vitesse de pointe" ou "nombre de sièges", où ces données spécifiques seraient stockées.

Méthodes

Les méthodes permettent à un objet de classe d'effectuer des opérations en utilisant les champs de la classe. Par exemple, dans le cas de la classe "Car", la méthode "refuel()" pourrait mettre à jour la propriété "fuelCapacity" de l'objet en le remplissant.

Avantages de l'utilisation des classes

Les classes sont le fondement de la programmation orientée objet en Java. Elles nous permettent de créer des objets et des applications complexes.

Les classes offrent une approche modulaire pour organiser le code d'une application. Différents composants peuvent être définis comme des classes distinctes et interagir les uns avec les autres via des interfaces. Ces composants peuvent également être réutilisés dans d'autres applications.

L'utilisation de classes facilite la maintenance du code, car il est plus facile d'apporter des modifications aux parties spécifiques d'une application en modifiant les classes correspondantes.

Déclaration et mise en œuvre

Le code écrit d'une classe et ses attributs sont connus comme la définition ou l'implémentation de la classe.

Déclaration

En Java, les classes sont définies de la manière suivante :

class ClassName { // Class name
  
  /* All member variables
  and methods*/
 
}

Le mot-clé class est utilisé pour indiquer au compilateur que nous sommes en train de créer une classe personnalisée. Tous les membres de la classe sont définis dans le contexte de la classe elle-même.

Création d'un objet de classe

Le nom de classe essentiel est "ClassName". Ce nom sera utilisé pour créer une instance de la classe dans le programme principal. Pour créer un objet d'une classe, on utilise le mot-clé "new".

class ClassName { // Class name
  ...
 
    // Main method
    public static void main(String args[]) {
    ClassName obj = new ClassName(); // className object
    
  }
}

Implémentation d'une Carclasse

Voici l'implémentation essentielle de la classe Car : diagclasse.png

//The Structure of a Java Class
class Car { // Class name
  
  // Class Data members
  int topSpeed;
  int totalSeats;
  int fuelCapacity;
  String manufacturer;
  
  // Class Methods
  void refuel(){
    ...
  }
  void park(){
    ...
  }
  void drive(){
    ...
  }
  
}

Modificateurs d'accès

En Java, il est possible d'imposer des restrictions d'accès sur les membres de données et les méthodes d'une classe en utilisant des modificateurs d'accès. Ces modificateurs permettent de définir quelles parties du programme peuvent accéder directement à ces membres.

Il existe trois types de modificateurs d'accès :

  1. Privé

Private : Un membre déclaré comme private n'est pas accessible directement depuis l'extérieur de la classe. L'objectif est de le garder caché des utilisateurs et des autres classes. Il est courant de déclarer les membres de données comme private pour empêcher toute manipulation directe. On utilise le mot-clé "private" pour rendre un membre privé. Il est important de noter que la restriction d'accès private vise à encapsuler les données et à les protéger en empêchant les accès non autorisés. copmain.png

class Cop {
 
private int gun; // We have explicitly defined that the variable is private
// ...
}
  1. Public

La balise "public" indique que les membres (variables ou méthodes) sont directement accessibles par tout ce qui se trouve dans la même portée que l'objet de classe. Les fonctions membres (méthodes) sont généralement déclarées comme publiques car elles fournissent l'interface par laquelle l'application peut communiquer avec les membres privés de la classe. Les membres publics peuvent être déclarés à l'aide du mot-clé "public". public.png

class Cop {
  private int gun; // Private variable
  
  public int getGun(){
    return gun;  // The private variable is directly accessible over here!
  }
}

Les membres publics d'une classe sont accessibles à travers un objet de cette classe en utilisant l'opérateur de point ".". Par exemple, si nous avons un objet "c" de type "Cop", nous pouvons accéder à la méthode "getGun()" de la manière suivante :

Cop c = new Cop(); // Object created
c.getGun(); // Can access the gun
c.gun = 0; // This would cause an error since gun is private
class Cop {
  private int gun; // Private variable
  
  public int getGun(){
    return gun;  // The private variable is directly accessible over here!
  }
}
class Main{
  public static void main(String args[]){
  Cop c = new Cop(); // Object created
  c.getGun(); // Can access the gun
  c.gun = 0; // This would cause an error since gun is private
  }
}
  1. Protégé

Le niveau d'accès "protected" est un niveau intermédiaire entre "private" et "public". Il est utilisé principalement dans le contexte de l'héritage, qui consiste à créer des classes à partir d'autres classes.

Les membres de données (variables) "protected" sont accessibles à l'intérieur du package Java dans lequel ils sont définis. Cependant, en dehors de ce package, ils peuvent être référencés uniquement par une classe qui hérite de la classe contenant les membres protégés.

package crime;  
import justice.*;
 
class Thief{  
public static void main(String args[]){  
Cop obj = new Cop();  
obj.fire(); //Compile Time Error  
}  
} 

La classe "Thief" générera une erreur de compilation car elle tente d'accéder à la méthode "fire()" de la classe "Cop" qui est définie dans un package différent.

par défaut

Lorsqu'aucun modificateur d'accès n'est spécifié pour un élément (variable, méthode, classe), il est considéré comme un accès par défaut. L'accès par défaut est similaire à l'accès "protected", car il permet un accès au niveau du package. Cependant, l'accès par défaut s'applique également aux classes héritées, contrairement à l'accès "protected". En résumé, l'accès par défaut a une portée plus limitée que l'accès "protected".

Des champs

Champs Java

En Java, les champs (fields) sont les membres de données à l'intérieur d'une classe. Par exemple, dans une classe représentant une voiture, on peut avoir les champs suivants :

topSpeed (vitesse maximale) totalSeats (nombre total de sièges) fuelCapacity (capacité du réservoir de carburant) Les champs représentent les données spécifiques à chaque objet de la classe et permettent de décrire les caractéristiques ou les propriétés de l'objet.

champ.png

La classe Java pourrait être définie comme ceci :

public class Car {
  
  int  topSpeed;
  int  totalSeats;
  int  fuelCapacity;
  
}

Champs statiques et non statiques

Java prend en charge les champs statiques et non statiques.

Champ statique

Un champ statique est un attribut qui réside dans une classe. Il est partagé par tous les objets créés à partir de cette classe, ce qui signifie qu'ils ont tous accès au même champ et à sa valeur.

static.png

class Car {
 
  // static fields
  static int topSpeed;
  static int maxCapacity;
 
}

Les champs statiques résident dans la classe elle-même. Ils peuvent être accédés sans avoir besoin d'instancier la classe. Pour accéder à un champ statique, il suffit d'écrire le nom de la classe suivi du nom du champ :

class Car {
// static fields
static int topSpeed = 100;
static int maxCapacity = 4;  
}
 
class Demo {
public static void main(String args[]){
// Static fields are accessible in the main
System.out.println(Car.topSpeed);
System.out.println(Car.maxCapacity);   
}
}
Champ non statique

Les champs non statiques d'une classe sont associés aux instances de la classe. Chaque instance de la classe peut avoir ses propres valeurs pour ces champs.

nonstatic.png

Voila comment définir un champ non statique

class Car {
  
  // Non-Static Fields
  int speed;
  int capacity;
 
}

Pour accéder aux champs non statiques d'une classe en Java, il est nécessaire de disposer d'une instance de cette classe. Les champs non statiques ne résident pas directement dans la classe elle-même, mais dans chaque instance individuelle créée à partir de cette classe. Par conséquent, il est obligatoire d'avoir une instance de la classe pour pouvoir accéder et manipuler les champs non statiques.

class Car {
    // static fields
    int speed = 100;
    int capacity = 4;  
}
 
class Demo {
    public static void main(String args[]){
    Car obj1 = new Car();
    System.out.println(obj1.speed);
    System.out.println(obj1.capacity);   
  }
}
Champs finaux

Un champ final est un attribut dont la valeur ne peut pas être modifiée une fois qu'elle a été assignée. Pour déclarer un champ comme final, on utilise le mot-clé "final".

class Car {
  // Final field of capacity = 4
  // Now Capacity can nerver be changed from 4
  // to some other value throught the program
  final int capacity = 4;
 
}

La classe "Carla" a une variable appelée "capacity" avec une valeur fixe de 4 qui ne peut pas être modifiée. Toute tentative de modification de cette valeur entraînera une erreur de compilation.

class Car {
  // Final variable capacity
  final int capacity = 4;
}
 
class Demo {
   public static void main(String args[]) {
      Car car = new Car();
      car.capacity = 5; // Trying to change the capacity value
   }
}

Méthodes

Le but des méthodes

Les méthodes agissent comme une interface entre un programme et les champs de données d'une classe. Elles peuvent modifier le contenu des champs ou utiliser leurs valeurs pour effectuer des calculs. Les méthodes importantes doivent être déclarées publiques, mais certaines méthodes non nécessaires à l'accès externe peuvent être déclarées privées.

Définition et déclaration

Une méthode est un bloc d'instructions qui réalise des opérations spécifiques. Elle peut ou non retourner un résultat.

class Car {
 
  // Public method to print speed
  public void printSpeed(int speed) {
    System.out.println("Speed: " + speed);
    }
  
}
 
class Demo {
  
  public static void main(String args[]) {
    Car car = new Car();
    car.printSpeed(100); // calling public method
  }
  
}

Paramètres de méthode et type de retour

Les paramètres de méthode sont utilisés pour transmettre des valeurs à une méthode, tandis que le type de retour est utilisé pour spécifier le type de valeur renvoyée par la méthode. Les paramètres sont déclarés entre parenthèses après le nom de la méthode, tandis que le type de retour est déclaré avant le nom de la méthode.

Déclaration de retour

Lorsque vous définissez une méthode avec un type de retour spécifié, l'instruction "return" doit être suivie immédiatement par la valeur de retour.

// public method with one parameter speed.
public int printSpeed(int speed) {
// ...
 
return speed + 5; // return statement
}

Par exemple, dans la méthode "printSpeed" ci-dessus, elle ajoute 5 au paramètre "speed" qui lui est passé, puis renvoie le résultat.

Le type de retour "int" qui est déclaré avant le nom de la méthode "printSpeed" indique que cette méthode renvoie une valeur de type entier (int).

Getters et setters

Les getters et les setters sont des méthodes couramment utilisées en programmation orientée objet. Un getter permet de récupérer la valeur d'un attribut spécifique, tandis qu'un setter permet de définir sa valeur.

Il est courant de nommer les méthodes getter et setter en utilisant le préfixe "get" ou "set" suivi du nom de l'attribut correspondant.

Prenons l'exemple de la classe "Car" et ajoutons les méthodes getter et setter pour l'attribut "speed" :

// Car class
class Car {
 
private int speed; // member field speed
 
// Setter method to set the speed of the car
public void setSpeed(int x) {
speed = x;
}
 
// Getter method to get the speed of the car
public int getSpeed() {
return speed;
}
 
}
 
class Demo {
 
public static void main(String args[]) {
Car car = new Car();
car.setSpeed(100); // calling the setter method
System.out.println(car.getSpeed()); // calling the getter method
}
 
}

Surcharge de méthode

La surcharge de méthode est utilisée pour permettre à une méthode d'effectuer différentes opérations en fonction des arguments fournis.

En Java, il est possible de surcharger des méthodes. Cela signifie que nous pouvons redéfinir une méthode plusieurs fois en lui donnant différents arguments et types de retour. Lorsque la méthode est appelée, le compilateur sélectionne la définition appropriée en fonction des arguments fournis.

Un exemple concret de surcharge de méthode peut être observé dans la classe Calculator, où la méthode "product" est surchargée avec différentes signatures :

class Calculator {
 
  public double product(double x, double y) {
    return x * y;
  }
 
  // Overloading the function to handle three arguments
  public double product(double x, double y, double z) {
    return x * y * z;
  }
 
  // Overloading the function to handle int
  public int product(int x, int y){
    return x * y;
  }
 
}
 
class Demo {
  
  public static void  main(String args[]) {
    Calculator cal = new Calculator();
    
    double x = 10;
    double y = 20;
    double z = 5;
    
    int a = 12;
    int b = 4;
    
    System.out.println(cal.product(x, y));
    System.out.println(cal.product(x, y, z));
    System.out.println(cal.product(a, b));
  }
  
}

Dans le code ci-dessus, nous observons que la même méthode se comporte différemment en fonction des types d'entrées qu' elle reçoit.

Il est important de noter que les méthodes qui n'ont pas d'arguments et ne diffèrent que par les types de retour ne peuvent pas être surchargées. Cela est dû au fait que le compilateur ne peut pas faire la distinction entre leurs appels.

Avantages de la surcharge de méthode

La surcharge de méthodes simplifie et rend le code propre en évitant de créer de nouvelles méthodes pour effectuer des tâches différentes. Le polymorphisme, un concept clé de la programmation orientée objet, est mis en œuvre grâce à la surcharge de méthodes. Cela permet de tirer parti de la flexibilité et de l'extensibilité offertes par l'approche orientée objet.

methodes.png

Constructeurs

Qu'est-ce qu'un constructeur ?

Le constructeur est une méthode spéciale utilisée pour créer des objets d'une classe. Il décrit les étapes exécutées lorsqu'une instance d'une classe est créée dans le programme.

Le nom du constructeur doit être identique au nom de la classe.

Le constructeur est une méthode spéciale car il n'a pas de type de retour. Il n'est même pas nécessaire de spécifier " void" comme type de retour. Il est recommandé de déclarer et de définir le constructeur comme la première méthode membre de la classe.

Il existe différents types de constructeurs qui peuvent être utilisés pour créer des objets de la classe.

Constructeur par défaut

Le constructeur par défaut est une forme simple de constructeur. Il permet de définir les valeurs par défaut des membres de données d'une classe. Ainsi, le constructeur crée un objet où les membres de données sont initialisés avec leurs valeurs par défaut.

Cela sera illustré par le code ci-dessous. Nous avons une classe appelée "Date" avec son constructeur par défaut, et nous créons un objet de cette classe dans notre méthode "main()":

class Date {
 
  private int day;
  private int month;
  private int year;
 
 
  // Default constructor
  public Date() {
    // We must define the default values for day, month, and year
    day = 0;
    month = 0;
    year = 0;
  }
 
  // A simple print function
  public void printDate(){ 
    System.out.println("Date: " + day + "/" + month + "/" + year);
  }
  
}
 
class Demo {
  
  public static void main(String args[]) {
    // Call the Date constructor to create its object;
    Date date = new Date(); // Object created with default values!
    date.printDate();
  }
  
}

Lors de la création d'un objet Date à la ligne 26, il n'est pas nécessaire de traiter le constructeur comme une méthode. Nous pouvons simplement écrire :

date.Date()

La création de l'objet se fait de la même manière que pour un objet Integer ou String.

Le constructeur par défaut n'a pas besoin d'être explicitement défini. Même si nous ne le créons pas, la JVM appellera automatiquement un constructeur par défaut qui initialise les variables membres à null ou à 0.

Si vous ne définissez aucun constructeur, le compilateur Java insérera automatiquement un constructeur par défaut pour vous. Ainsi, une fois la classe compilée, elle aura toujours au moins un constructeur sans argument.

Constructeur paramétré

Le constructeur par défaut n'est pas très remarquable. Il est possible d'utiliser les méthodes "set" pour définir les valeurs des membres de données "day", "month" et "year" individuellement. Cependant, cette étape peut être évitée en utilisant un constructeur avec des paramètres.

Dans un constructeur avec des paramètres, nous passons des arguments au constructeur et les utilisons pour définir les valeurs de nos membres de données.

En somme, nous surchargeons le constructeur par défaut afin de lui permettre d'accueillir nos valeurs préférées pour les membres de données.

class Date {
 
  private int day;
  private int month;
  private int year;
 
 
  // Default constructor
  public Date() {
    // We must define the default values for day, month, and year
    day = 0;
    month = 0;
    year = 0;
  }
 
  // Parameterized constructor
  public Date(int d, int m, int y){
    // The arguments are used as values
    day = d;
    month = m;
    year = y;
  }
 
  // A simple print function
  public void printDate(){ 
    System.out.println("Date: " + day + "/" + month + "/" + year);
  }
}
 
class Demo {
  
  public static void main(String args[]) {
    // Call the Date constructor to create its object;
    Date date = new Date(1, 8, 2018); // Object created with specified values! // Object created with default values!
    date.printDate();
  }
  
}

Un peu plus sur les constructeurs

La variable de référence "this" est présente dans chaque classe et fait référence à l'objet de la classe elle-même. Nous utilisons "this" lorsque nous avons un argument qui porte le même nom qu'un membre de données. "this.memberName" permet d'accéder à la variable "memberName" spécifique de la classe.

class Date {
 
  private int day;
  private int month;
  private int year;
 
 
  // Default constructor
  public Date() {
    // We must define the default values for day, month, and year
    this.day = 0;
    this.month = 0;
    this.year = 0;
  }
 
  // Parameterized constructor
  public Date(int day, int month, int year){
    // The arguments are used as values
    this.day = day;
    this.month = month;
    this.year = year;
  }
 
  // A simple print function
  public void printDate(){ 
    System.out.println("Date: " + day + "/" + month + "/" + year);
  }
  
}
 
class Demo {
  
  public static void main(String args[]) {
    // Call the Date constructor to create its object;
    Date date = new Date(1, 8, 2018); // Object created with specified values! // Object created with default values!
    date.printDate();
  }
  
}

Appeler un constructeur depuis un constructeur

En Java, il est possible d'appeler un constructeur à partir d'un autre constructeur de la même classe. Lorsque cela se produit, le mot-clé "this" est utilisé pour faire référence au constructeur actuel.

Voyons un exemple pour mieux comprendre :

class Date {
 
  private int day;
  private int month;
  private int year;
  private String event;
 
 
  // Default constructor
  public Date() {
    // We must define the default values for day, month, and year
    this.day = 0;
    this.month = 0;
    this.year = 0;
  }
 
  // Parameterized constructor
  public Date(int day, int month, int year){
    // The arguments are used as values
    this.day = day;
    this.month = month;
    this.year = year;
  }
  
  // Parameterized constructor
  public Date(int day, int month, int year, String event){
    this(day, month, year); // calling the constructor
    this.event = event;
  }
 
  // A simple print function
  public void printDate(){ 
    System.out.println("Date: " + day + "/" + month + "/" + year + "  --> " + event);
  }
  
}
 
class Demo {
  
  public static void main(String args[]) {
    // Call the Date constructor to create its object;
    Date date = new Date(1, 1, 2019, "New Year"); // Object created with specified values! // Object created with default values!
    date.printDate();
  }
  
}

Le mot-clé "this" suivi de parenthèses est utilisé pour appeler un autre constructeur de la même classe Java. À la ligne 27, le deuxième constructeur de la classe est appelé à l'aide de "this".

Masquage des données

Qu'est-ce que le masquage de données ?

Présentation

En programmation orientée objet, les objets et les classes sont les entités de base. Les objets sont créés à partir de classes. Les classes contiennent des membres de données, et les objets sont utilisés pour manipuler et accéder à ces données. Pour assurer la fiabilité et la sécurité du système orienté objet, il est recommandé de limiter l'accès aux membres de classe.

En termes simples, la dissimulation de données consiste à masquer le fonctionnement interne d'une classe et à fournir une interface permettant aux utilisateurs externes d'interagir avec la classe sans connaître les détails internes.

L'objectif est d'implémenter des classes de manière à ce que les instances (objets) de ces classes ne puissent pas accéder ou modifier de manière non autorisée le contenu original de la classe. Une classe n'a pas besoin de connaître les algorithmes internes d'une autre classe, mais les deux peuvent toujours communiquer entre elles.

Un exemple concret est le modèle médecin-patient. Lorsqu'un patient est malade, il consulte un médecin qui lui prescrit un médicament approprié.

Le patient ne connaît que la procédure de consultation médicale, sans comprendre les détails du raisonnement médical derrière la prescription du médicament. Le patient ne sait pas quels critères le médecin utilise pour prendre cette décision de traitement.

Cet exemple illustre comment la classe "patient" interagit avec la classe "médecin" sans connaître les détails internes de cette dernière.

doc1.png doc2.png doc3.png doc4.png doc5.png

La transaction ci-dessus semble assez simple. Le masquage des données est utile car il peut appliquer la même simplicité aux transactions entre objets de classes différentes.

Composants du masquage des données

Le masquage de données peut être divisé en deux composants principaux :

  • Encapsulation
  • Abstraction

datahidding.png Il s'agissait essentiellement de dissimulation de données et des techniques associées.

Encapsulation

Définition

L'encapsulation est une technique essentielle de la programmation orientée objet (POO) qui permet de masquer les données et de les lier avec les méthodes qui les manipulent.

En utilisant l'encapsulation, les données et les méthodes sont regroupées dans une unité logique appelée classe. À partir de cette classe, des objets peuvent être créés. L'encapsulation vise généralement à masquer l'état et la représentation interne d'un objet, en limitant l'accès aux données uniquement par les méthodes définies dans la classe.

Ainsi, une classe peut être considérée comme une capsule contenant des membres de données et des méthodes associées, permettant de manipuler ces données de manière contrôlée.

encapsulation.png

En règle générale, il est recommandé de déclarer les variables membres ou les variables d'instance d'une classe en tant que "private". Cela restreint l'accès direct à ces variables depuis le code en dehors de la classe.

Cependant, pour permettre l'utilisation des variables et des méthodes de la classe depuis l'extérieur, il est nécessaire de mettre en place des méthodes publiques. Ces méthodes, telles que les getters, les setters et autres méthodes personnalisées, permettent à d'autres parties du code d'interagir avec la classe de manière contrôlée et sécurisée.

encapsualtion2.png

Avantages de l'encapsulation

  • Les classes sont plus faciles à changer et à maintenir.
  • Nous pouvons spécifier quel membre de données nous voulons garder caché ou accessible.
  • Nous décidons quelles variables ont des privilèges de lecture/écriture (augmente la flexibilité).

Comprendre l'encapsulation à l'aide d'exemples

L'encapsulation est le concept de regrouper les données et les méthodes qui opèrent sur ces données dans une unité appelée classe. Son objectif est de restreindre l'accès indésirable aux données liées en dehors de la classe. Un exemple basique est donné avec la classe User qui modélise un utilisateur d'une application.

La classe User comprend les éléments suivants :

  • Un attribut pour le nom d'utilisateur (userName).
  • Un attribut pour le mot de passe (password).
  • Une méthode appelée login() pour accorder l'accès.

Chaque fois qu'un nouvel utilisateur arrive, un nouvel objet peut être créé en passant le userName et le password au constructeur de la classe User.

encapsulation3.png

Un mauvais exemple

Il est maintenant temps d'implémenter la Userclasse décrite ci-dessus.

// User Class
class User {
 
  // Public Fields
  public String userName;
  public String password;
 
  // Parameterized Constructor to create new users
  public User(String userName, String password) { 
    this.userName = userName;
    this.password = password;
  }
 
  public void login(String userName, String password) {
    //Check if the username and password match
    if (this.userName.toLowerCase().equals(userName.toLowerCase()) && this.password.equals(password)) { 
    // .toLowrcase converts all the characters to lowercase & .equals checks if two strings match
 
      System.out.println("Access Granted against the username: "+this.userName +" and password: "+this.password);
    }
    else System.out.println("Invalid Credentials!"); //Else invalid credentials
  }
 
}
 
class Main {
  
  public static void main(String[] args) {
    User educative = new User("Educative","12345"); //Creates a new user and stores the password and username
 
    educative.login("Educative","12345"); //Grants access because credentials are valid
 
    educative.login("Educative", "3456"); //Does not grant access because the credentials are invalid
 
    educative.password = "3456"; //OOPS SOMEONE ACCESSED THE PASSWORD FIELD
 
    educative.login("Educative", "3456"); // GRANTS ACCESS BUT THIS SHOULD NOT HAVE HAPPENED!
  }
  
}

Dans l'exemple de code précédent, il est observé que les champs password et userName peuvent être accessibles, modifiés ou imprimés directement depuis la méthode main(). Cela représente un risque de sécurité dans le cas de la classe User, car il n'y a pas d'encapsulation des informations d'identification d'un utilisateur. Cela signifie que n'importe qui peut accéder aux comptes des utilisateurs en manipulant les données stockées. Ainsi, le code présenté ne suit pas une bonne convention de codage.

Un bon exemple

une bonne convention d'implémentation de la Userclasse !

encapsulation4.png

// User Class
class User {
 
  // Private fields
  private String userName; 
  private String password;     
 
  //Parameterzied constructor to create a new users
  public User(String userName, String password) {    
    this.userName = userName;
    this.password = password;
  }
 
  public void login(String userName, String password) {
    //Check if the username and password match
    if (this.userName.toLowerCase().equals(userName.toLowerCase()) && this.password.equals(password)) { 
    // .toLowrcase converts all the characters to lowercase & .equals checks if two strings match
 
      System.out.println("Access Granted against the username: "+this.userName +" and password: "+this.password);
    }
    else System.out.println("Invalid Credentials!"); //Else invalid credentials
  }
 
}
 
class Main {
  
  public static void main(String[] args) {
    User educative = new User("Educative","12345"); //Creates a new user and stores the password and username
 
    educative.login("Educative","12345"); //Grants access because credentials are valid
 
    educative.login("Educative", "3456"); //Does not grant access because the credentials are invalid
 
    //educative.password = "3456"; //Uncommenting this line will give an error
                                   //Fields of User class cannot be accessed now
  }
  
}

Dans l'exemple mentionné, les champs userName et password sont déclarés en tant que private.

En suivant la bonne pratique, dans une classe, tous les membres variables devraient être déclarés en tant que private, tandis que les méthodes publiques, telles que les getters, les setters et les méthodes personnalisées, sont utilisées pour accéder et manipuler ces données.

Ceci est connu sous le concept d'encapsulation. Les champs de données sont maintenus privés, et les méthodes publiques fournissent une interface pour accéder à ces champs.

Héritage

Qu'est-ce que l'héritage ?

Définition

L'héritage est utilisé pour créer une nouvelle classe à partir d'une classe existante. La nouvelle classe est une version spécialisée de la classe existante et hérite de tous les champs non privés (variables) et méthodes de la classe existante. La classe existante sert de point de départ ou de base pour la création de la nouvelle classe.

L'héritage est utilisé lorsque nous avons une relation "EST UN" entre les objets. Cela signifie que si nous pouvons dire qu'un objet est une version spécifique d'un autre objet plus général, alors l'héritage peut être utilisé.

En utilisant l'héritage, nous pouvons réutiliser le code existant, éviter la duplication et organiser les classes de manière hiérarchique, ce qui facilite la compréhension et la maintenance du code.

heritage1.png

Dans l'illustration ci-dessus, nous observons trois objets qui ont une relation "EST UN" entre eux. Nous pouvons les exprimer comme suit :

  • Un carré "EST UNE" forme.
  • Java "EST UN" langage de programmation.
  • La voiture "EST UN" véhicule.

Ainsi, à partir de ces exemples d'héritage, nous pouvons conclure que nous pouvons créer de nouvelles classes en se basant sur des classes existantes. Cela nous permet de développer de nouvelles classes.

heritage2.png

Découvrons où une relation IS A n'existe pas. heritage3.png

Dans l'illustration ci-dessus, il est important de noter qu'il n'est pas possible d'utiliser l'héritage car il n'existe pas de relation "EST-UN" entre les objets.

La classe Java Object : En Java, l'objectif principal de la programmation orientée objet est de permettre aux programmeurs de modéliser les objets du monde réel à l'aide d'un langage de programmation.

En Java, chaque fois que nous créons une classe, elle hérite automatiquement de toutes les méthodes et champs non privés de la classe Object intégrée. Cela en fait un exemple concret d'héritage en Java. Les méthodes définies dans la classe Object sont très utiles lors de la création de nouvelles classes.

La syntaxe et les terminologies

Les terminologies

En programmation orientée objet, lorsqu'une nouvelle classe est créée en se basant sur une classe existante (dans ce qu'on appelle l'héritage), on utilise les termes suivants pour décrire la nouvelle classe et la classe existante :

  • SuperClass (Classe parente ou classe de base) : Cette classe permet la réutilisation de ses membres non privés dans une autre classe. Elle sert de point de départ pour la création de classes dérivées.

  • Sous-classe (Classe enfant ou classe dérivée) : Cette classe est celle qui hérite de la superclasse. Elle peut accéder et utiliser les membres non privés de la classe parente. Elle peut également ajouter de nouveaux membres ou modifier le comportement hérité.

Il est important de noter que la sous-classe possède toutes les caractéristiques non privées de la classe parente et peut les utiliser à sa propre convenance.

Cette relation d'héritage permet une meilleure organisation et réutilisation du code, en évitant la redondance et en favorisant la création de hiérarchies de classes bien structurées.

Que contient une classe enfant ?

Un objet de la classe enfant peut accéder aux éléments suivants :

  • Tous les membres non privés définis dans la classe enfant.
  • Tous les membres non privés définis dans la classe parent.

Il est important de noter que certaines classes ne peuvent pas être héritées. Ces classes sont définies avec le mot-clé "final". Par exemple, la classe intégrée "Integer" est une classe finale, ce qui signifie qu'elle ne peut pas être utilisée comme classe parente pour créer des classes dérivées.

Le extendsmot-clé

En Java, nous devons utiliser le mot clé extendspour implémenter l'héritage :

SubClass extends SuperClass{
//contents of SubClass
 

Prenons l'exemple d'une classe de base appelée "Vehicle" et implémentons une classe "Car" qui étend cette classe " Vehicle". Étant donné que une voiture est un véhicule, l'utilisation de l'héritage entre ces classes est appropriée.

// Base Class Vehicle
class Vehicle {
 
  // Private Fields
  private String make; 
  private String color; 
  private int year;      
  private String model;   
 
 
  // Parameterized Constructor
  public Vehicle(String make, String color, int year, String model) {
    this.make = make;
    this.color = color;
    this.year = year;  
    this.model = model; 
  }
 
  // public method to print details
  public void printDetails() {
    System.out.println("Manufacturer: " + make);
    System.out.println("Color: " + color);
    System.out.println("Year: " + year);
    System.out.println("Model: " + model);
  }
  
}
 
// Derived Class Car
class Car extends Vehicle {
 
  // Private field
  private String bodyStyle;
 
  // Parameterized Constructor
  public Car(String make, String color, int year, String model, String bodyStyle) {
    super(make, color, year, model);  //calling parent class constructor
    this.bodyStyle = bodyStyle;       
  }
 
  public void carDetails() {  //details of car
    printDetails();         //calling method from parent class
    System.out.println("Body Style: " + bodyStyle);
  }
  
}
 
class Main {
 
  public static void main(String[] args) {
    Car elantraSedan = new Car("Hyundai", "Red", 2019, "Elantra", "Sedan"); //creation of car Object
    elantraSedan.carDetails(); //calling method to print details
  }
  
}
 

Super mot-clé

Quel est le supermot clé ?

Le mot-clé "this" en Java est utilisé pour faire référence à l'instance de la classe actuelle.

De même, le mot-clé "super" en Java est utilisé pour faire référence aux membres de la superclasse à l'intérieur de la sous-classe immédiate. Il est utilisé lorsque nous mettons en œuvre l'héritage.

Le mot-clé "super" est utilisé dans trois contextes principaux :

Accéder aux champs de la classe parent : Lorsque nous avons des champs définis dans la classe parente (superclasse) et que nous voulons y accéder depuis la sous-classe, nous utilisons le mot-clé "super". Cela se produit souvent lorsque nous avons des noms de champs identiques dans la superclasse et la sous-classe, et que nous voulons accéder spécifiquement au champ de la superclasse. Ces concepts peuvent être mieux compris en examinant un exemple de code.

class Vehicle { //Base class vehicle
 
int fuelCap = 90; //fuelCap field inside SuperClass
 
}
 
 
class Car extends Vehicle { // sub class Car extending from Vehicle
 
int fuelCap = 50; //fuelCap field inside SubClass
 
public void display() {
//accessing the field of parent class using super*/
System.out.println("Fuel Capacity from the Vehicle class: " + super.fuelCap);
//without using super the field of current class shadows the field of parant class*/
System.out.println("Fuel Capacity from the Car class: " + fuelCap);
 
}
 
}
 
class Main {
 
public static void main(String[] args) {
Car corolla = new Car();
corolla.display();
}
 
}

Appel d'une méthode de classe parent

Lorsque des méthodes portent le même nom dans une SuperClass et une SubClass immédiate, le mot-clé "super" est utilisé pour accéder aux méthodes de la SuperClass à l'intérieur de la SubClass.

Voici un exemple pour illustrer cela :

class Vehicle {          //Base class vehicle 
 
  public void display() {   //display method inside SuperClass
    System.out.println("I am from the Vehicle Class");
  }
 
} 
 
class Car extends Vehicle { // sub class Car extending from Vehicle
 
  public void display() { //display method inside SubClass
    System.out.println("I am from the Car Class");
  } 
 
  public void printOut(){
    System.out.println("The display() call with super:");
    super.display();  //calling the display() of Vehicle(SuperClass)
    System.out.println("The display() call without super:");
    display();        //calling the display() of the Car(SubClass)
  }
 
} 
 
class Main {
 
  public static void main(String[] args) {
    Car corolla = new Car(); 
    corolla.printOut(); 
  }
 
}

Utilisation avec les constructeurs

Le mot-clé "super" est utilisé pour appeler le constructeur de la superclasse à partir du constructeur de la sous-classe. C'est une utilisation importante du mot-clé "super".

Il est important de noter qu'en créant un objet de type sous-classe, un objet de type superclasse est également créé implicitement en appelant le constructeur de la superclasse.

La syntaxe pour appeler le constructeur est la suivante :

super();  //calls the (no argument) constructor if a no-argument constructor is defined in the SuperClass
 
super(parameters); //calls the parameterized constructor of the SuperClass with matching parameters from the SubClass constructor

Les lignes ci-dessus représentent la syntaxe générale pour l'appel du constructeur de la classe parente (SuperClass).

L'appel au constructeur de la classe parente à l'aide de super() est généralement la première ligne de code à l'intérieur du constructeur de la sous-classe. Si nous n'appelons pas super() dans le constructeur de la sous-classe, le constructeur par défaut sans argument de la classe parente sera appelé automatiquement. L'appel super(parameters) doit être utilisé si nous voulons appeler un constructeur paramétré de la classe parente.

Voici un exemple d'appel de constructeur utilisant super().

Veuillez noter que le code ci-dessous générera une erreur car il n'y a pas d'appel au constructeur de la classe parente à partir du constructeur de la sous-classe.

class Vehicle {              //base class of vehicle
  
    private String make;    //
    private String color;   // Vehicle Fields
    private int year;       //
    private String model;   //
 
 
    public Vehicle(String make, String color, int year, String model) {
        this.make = make;    //
        this.color = color;  // Constructor of Vehicle
        this.year = year;    //
        this.model = model;  //
    }
 
    public void printDetails() {  //public method to print details
        System.out.println("Manufacturer: " + make);
        System.out.println("Color: " + color);
        System.out.println("Year: " + year);
        System.out.println("Model: " + model);
    }
  
}
 
class Car extends Vehicle {    //derived class of Car
 
    private String bodyStyle;  //Car field
 
    public Car(String make, String color, int year, String model, String bodyStyle) {
        //super(make, color, year, model);  //parent class constructor
        this.bodyStyle = bodyStyle;       
    }
 
    public void carDetails() {  //details of car
        printDetails();         //calling method from parent class
        System.out.println("Body Style: " + bodyStyle);
    }
  
}
class Main {
 
    public static void main(String[] args) {
        Car elantraSedan = new Car("Hyundai", "Red", 2019, "Elantra", "Sedan"); //creation of car Object
        elantraSedan.carDetails(); //calling method to print details
    }
  
}

Exécution réussie :

class Vehicle {              //base class of vehicle
  
    private String make;    //
    private String color;   // Vehicle Fields
    private int year;       //
    private String model;   //
 
 
    public Vehicle(String make, String color, int year, String model) {
        this.make = make;    //
        this.color = color;  // Constructor of Vehicle
        this.year = year;    //
        this.model = model;  //
    }
 
    public void printDetails() {  //public method to print details
        System.out.println("Manufacturer: " + make);
        System.out.println("Color: " + color);
        System.out.println("Year: " + year);
        System.out.println("Model: " + model);
    }
  
}
 
class Car extends Vehicle {    //derived class of Car
 
    private String bodyStyle;  //Car field
 
    public Car(String make, String color, int year, String model, String bodyStyle) {
        super(make, color, year, model);  //parent class constructor
        this.bodyStyle = bodyStyle;       
    }
 
    public void carDetails() {  //details of car
        printDetails();         //calling method from parent class
        System.out.println("Body Style: " + bodyStyle);
    }
  
}
 
class Main {
 
    public static void main(String[] args) {
        Car elantraSedan = new Car("Hyundai", "Red", 2019, "Elantra", "Sedan"); //creation of car Object
        elantraSedan.carDetails(); //calling method to print details
    }
  
}

Notez qu'il est possible d'inclure un appel à super() ou this() dans un constructeur, mais pas les deux en même temps. De plus, ces appels ne peuvent être utilisés que dans le contexte des constructeurs.

Types d'héritage

Sur la base des superclasses et des sous-classes, il existe en général les cinq types d'héritage suivants :

  1. Seul
  2. Multi-niveaux
  3. Hiérarchique
  4. Plusieurs
  5. Hybride

Héritage unique

Dans l'héritage unique, il n'y a qu'une seule classe s'étendant à partir d'une autre classe. On peut prendre l'exemple de la Vehicleclasse (Super classe) et de la Carclasse (Sous classe). Implémentons ces classes ci-dessous : heritageseul.png

Héritage à plusieurs niveaux

Lorsqu'une classe est dérivée d'une telle classe qui est elle-même dérivée d'une autre classe, ce type d'héritage est appelé héritage multiniveau. Les classes peuvent être étendues à tous les niveaux supplémentaires selon les exigences du modèle.

heritagemulitpple.png Implémentons les trois classes illustrées ci-dessus :

A Car IS A Vehicle A Prius IS A Car

Héritage hiérarchique

Lorsque plusieurs classes héritent de la même classe, on parle d'héritage hiérarchique. Dans l'héritage hiérarchique, plusieurs classes s'étendent, selon les exigences de la conception, à partir de la même classe de base. Les attributs communs de ces classes enfants sont implémentés à l'intérieur de la classe de base. Example:

A Car IS A Vehicle A Truck IS A Vehicle heritagehierarchique.png

Héritage multiple

Lorsqu'une classe est dérivée de plusieurs classes de base, c'est-à-dire lorsqu'une classe a plusieurs classes parentes immédiates, ce type d'héritage est appelé héritage multiple. Example:

A Hyundai Elantra IS A Car. A Hyundai Elantra IS A Sedan also.

heritagemulitpple.png

Héritage hybride

Un type d'héritage qui est une combinaison d' héritage multiple et multi-niveaux est appelé héritage hybride .

  • Un moteur à combustion est un moteur
  • Un moteur de moteurs électriques est un moteur
  • Un moteur hybride combine à la fois un moteur à combustion et des moteurs électriques.

heritagehybride.png

Remarque : En Java, l'héritage multiple et hybride s'applique uniquement à l'aide d'interfaces.

Avantages de l'héritage

Réutilisabilité

L'héritage permet la réutilisabilité du code. Dans le contexte d'un système bancaire, on peut concevoir un modèle avec les éléments suivants :

Une classe de base appelée BankAccount (compte bancaire). Une classe dérivée appelée SavingsAccount (compte d'épargne). Une classe dérivée appelée CheckingAccount (compte chèque). En utilisant l'héritage, les classes dérivées (SavingsAccount et CheckingAccount) peuvent hériter des caractéristiques et du comportement de la classe de base (BankAccount). Cela permet une réutilisabilité du code, car les fonctionnalités communes sont définies une seule fois dans la classe de base et peuvent être utilisées par les classes dérivées sans avoir à les redéfinir.

Cela facilite la conception et la maintenance du système bancaire, car les fonctionnalités spécifiques aux comptes d'épargne et aux comptes chèques peuvent être ajoutées dans les classes dérivées tout en bénéficiant des fonctionnalités communes fournies par la classe de base. heritageAvantage.png

À un certain moment, il peut être nécessaire de diversifier une application bancaire en ajoutant une autre classe appelée "MoneyMarketAccount". Plutôt que de commencer l'implémentation de cette classe à partir de zéro, une approche plus efficace consiste à étendre la classe existante "BankAccount" et à réutiliser les attributs communs entre les deux classes.

Dans cet exemple, l'héritage est utilisé pour éviter la redondance de code et pour économiser du temps et des efforts pour le programmeur. Cela permet de démarrer avec une classe existante (BankAccount) et d'en étendre les fonctionnalités pour créer la classe MoneyMarketAccount.

Éviter la duplication du code

Dans l'exemple ci-dessus, il n'est pas nécessaire de dupliquer le code des méthodes deposit() et withdraw() à l'intérieur des classes enfants, telles que SavingsAccount et CheckingAccount. Cela permet d'éviter la duplication de code. ctrlcctrclv.png

Extensibilité

L'héritage permet d'étendre la logique d'une classe de base en fonction des besoins métier spécifiques d'une classe dérivée. C'est un moyen de mettre à niveau ou d'améliorer certaines parties d'un produit sans modifier les attributs de base. Une classe existante peut être utilisée comme classe de base pour créer une nouvelle classe qui possède des fonctionnalités améliorées. heritagetetris.png

Masquage des données

La classe de base peut décider de garder certaines données privées afin qu'elles ne puissent pas être modifiées par la classe dérivée. Ce concept est connu sous le nom d'encapsulation.

masquagedonnees.png

Polymorphisme

Qu'est-ce que le polymorphisme ?

Définition

Le polymorphisme en programmation fait référence à un même objet pouvant prendre différentes formes et comportements.

Prenons l'exemple d'une classe de base appelée Shape, à partir de laquelle les sous-classes Rectangle, Circle, Polygon et Diamond sont dérivées. Chaque sous-classe peut implémenter la méthode calculateArea() de manière spécifique.

Ainsi, lorsque la méthode calculateArea() est appelée sur un objet de la classe Rectangle, elle peut calculer et afficher l'aire du rectangle. Lorsque la même méthode est appelée sur un objet de la classe Circle, elle peut calculer et afficher l'aire du cercle.

Le polymorphisme permet au développeur de créer des sous-classes spécifiques avec des attributs et des comportements uniques, en modifiant uniquement les parties du code où les réponses diffèrent. Le reste du code peut rester inchangé, ce qui facilite le travail du développeur. polymorphisme.png

Polymorphisme en POO

Le polymorphisme est un concept fondamental de la programmation orientée objet qui permet à une classe dérivée d'hériter d'une méthode de sa classe de base tout en ayant une implémentation différente pour cette méthode.

Prenons l'exemple de la classe Shape, qui est la classe de base, et des classes dérivées comme Rectangle et Circle. Chacune de ces classes contient une méthode getArea() qui calcule la surface de la forme correspondante.

Lorsque le polymorphisme est utilisé, nous pouvons traiter des objets de différentes classes dérivées comme des objets de la classe de base. Par exemple, nous pouvons avoir une liste d'objets Shape qui contient à la fois des objets Rectangle et des objets Circle.

Lorsque nous appelons la méthode getArea() sur ces objets, la version appropriée de la méthode (celle définie dans la classe dérivée respective) est appelée, en fonction du type réel de l'objet. Cela permet d'obtenir le calcul correct de la surface pour chaque forme.

Le polymorphisme permet une flexibilité et une extensibilité accrues dans la conception des classes, en permettant aux classes dérivées de personnaliser et de spécialiser les comportements hérités de la classe de base.

polyyy.png

Dans notre mise en œuvre, nous avons une classe de base appelée "Shape" et des classes dérivées qui en héritent.

La classe "Shape" possède une méthode publique essentielle nommée "getArea()".

Voici un aperçu de l'implémentation de la classe "Shape".

// A simple Shape which provides a method to get the Shape's area
class Shape {
  
  public double getArea(){}
  
}

La classe Rectangle est une extension de la classe Shape. Elle possède deux membres de données, width et height, qui représentent respectivement la largeur et la hauteur du rectangle. La classe Rectangle a également une méthode getArea() qui renvoie l'aire du rectangle.

Voici une implémentation de la classe Rectangle :

// A Rectangle is a Shape with a specific width and height
class Rectangle extends Shape {   // derived form Shape class
  
  // Private data members
  private double width;
  private double height;
 
  // Constructor
  public Rectangle(double width, double height) {
    this.width = width;
    this.height = height;
  }
  
  // Public method to calculate Area
  public double getArea() {
    return width * height; 
  }
  
}

La classe Circle est une sous-classe de la classe Shape. Elle possède un unique membre de données, "radius", qui est utilisé pour calculer l'aire du cercle à l'aide de la méthode "getArea()".

Voici une implémentation possible de la classe Circle :

// A Circle is a Shape with a specific radius
class Circle extends Shape {
  
  // Private data member
  private double radius;
 
  // Constructor
  public Circle(double radius) {
    this.radius = radius; 
  }
  
  // Public method to calculate Area
  public double getArea() {
    return 3.14 * radius * radius; 
  }
  
}

Programme complet Maintenant, en fusionnant toutes les classes et en appelant la getArea()méthode, voyez ce qui se passe :

// A sample class Shape which provides a method to get the Shape's area
 
class Shape {
 
  public double getArea() {
    return 0;
  }
  
}
 
// A Rectangle is a Shape with a specific width and height
class Rectangle extends Shape {   // extended form the Shape class
 
  private double width;
  private double height;
 
  public Rectangle(double width, double heigh) {
    this.width = width;
    this.height = heigh;
  }
 
  public double getArea() {
    return width * height; 
  }
  
}
 
// A Circle is a Shape with a specific radius
class Circle extends Shape {
  private double radius;
 
  public Circle(double radius) {
    this.radius = radius; 
  }
  public double getArea() {
    return 3.14 * radius * radius; 
  }
  
}
 
 
class driver {
 
  public static void main(String args[]) {
    Shape[] shape = new Shape[2]; // Creating shape array of size 2
 
    shape[0] = new Circle(2); // creating circle object at index 0
    shape[1] = new Rectangle(2, 2); // creating rectangle object at index 1
 
    System.out.println("Area of the Circle: " + shape[0].getArea());
    System.out.println("Area of the Rectangle: " + shape[1].getArea());
  }
 
}

Dans la fonction main, nous avons créé un tableau de classes appelé "Shape" avec une taille de 2. Nous avons ensuite instancié des objets de classe "Circle" et "Rectangle" respectivement aux indices 0 et 1 du tableau. La méthode " getArea()" est utilisée pour obtenir la surface de chaque forme respective. Cela illustre le concept de polymorphisme.

Remplacer la méthode

Une brève présentation

Le remplacement de méthode est le processus de redéfinition d'une méthode d'une classe parente dans une sous-classe.

En d'autres termes, lorsque la sous-classe fournit une implémentation spécifique d'une méthode déjà déclarée dans l'une de ses classes parentes, on parle de remplacement de méthode.

Cela permet à une classe enfant de fournir sa propre implémentation d'une méthode qui est déjà fournie par la classe parente.

Par exemple, les classes Rectangle et Circle peuvent remplacer la méthode getArea() de la classe Shape.

Dans ce contexte :

  • La méthode de la classe parente est appelée méthode surchargée.
  • Les méthodes des classes enfants sont appelées méthodes de substitution.
  • Les parties en surbrillance dans l'extrait de code ci-dessous montrent où se produit le remplacement de méthode.

polyreplace.png

// A sample class Shape which provides a method to get the Shape's area
class Shape {
 
  public double getArea() {
    return 0;
  }
  
}
 
// A Rectangle is a Shape with a specific width and height
class Rectangle extends Shape {   // extended form the Shape class
 
  private double width;
  private double height;
 
  public Rectangle(double width, double height) {
    this.width = width;
    this.height = height;
  }
 
  public double getArea() {
    return width * height; 
  }
}
 
// A Circle is a Shape with a specific radius
class Circle extends Shape {
  
  private double radius;
 
  public Circle(double radius) {
    this.radius = radius; 
  }
  public double getArea() {
    return 3.14 * radius * radius; 
  }
  
}
 
 
class driver {
 
  public static void main(String args[]) {
    Shape[] shape = new Shape[2]; // Creating shape array of size 2
 
    shape[0] = new Circle(2); // creating circle object at index 0
    shape[1] = new Rectangle(2, 2); // creating rectangle object at index 1
 
    // Shape object is calling children classes method
    System.out.println("Area of the Circle: " + shape[0].getArea());
    System.out.println("Area of the Rectangle: " + shape[1].getArea());
  }
 
}

L'utilisation de la méthode de remplacement (ou "override" en anglais) présente plusieurs avantages dans la programmation orientée objet. Voici les principaux points à retenir :

  • Les classes dérivées peuvent fournir leurs propres implémentations spécifiques pour les méthodes héritées, sans avoir à modifier les méthodes de la classe parente.
  • Pour n'importe quelle méthode, une classe dérivée peut choisir d'utiliser l'implémentation de la classe parente ou de créer sa propre implémentation.

Voici quelques caractéristiques importantes de la méthode de remplacement :

  • Le remplacement de méthode nécessite un mécanisme d'héritage, où une classe dérive d'une autre.

  • Les classes dérivées doivent déclarer une méthode ayant la même signature que la méthode de la classe parente, y compris le modificateur d'accès, le nom, les paramètres et le type de retour.

  • Les méthodes dans les classes dérivées doivent avoir des implémentations différentes les unes des autres.

  • La méthode de la classe de base doit être "remplacée" (override) dans la classe dérivée.

  • Il est important de noter que la classe/méthode de base ne doit pas être déclarée comme étant "final" (c'est-à-dire non extensible).

Ces éléments clés du remplacement de méthode permettent aux classes dérivées d'ajouter ou de modifier le comportement hérité de manière flexible, selon les besoins spécifiques de chaque classe.

Différence entre la surcharge et le remplacement des méthodes

Surcharge de méthode et remplacement de méthode

La surcharge et le remplacement de méthode sont deux concepts complètement différents. polysurcharge.png

Comparons les différences ci-dessous : polysurchargecomparaison.png

Exemple de surcharge polymorhCalculator.png

//Calculator Class
class Calculator {
 
  // Add funtions with two parameters
  int add(int num1, int num2) {
    return num1 + num2;
  }
 
  // Add funtions with three parameters
  int add(int num1, int num2, int num3 ) {
    return num1 + num2 + num3;
  }
 
  // Add funtions with four parameters
  int add(int num1, int num2, int num3, int num4 ) {
    return num1 + num2 + num3 + num4;
  }
 
  public static void main(String args[]) {
    Calculator cal = new Calculator();
 
    System.out.println("10 + 20 =  " + cal.add(10, 20));
    System.out.println("10 + 20 + 30 =  " + cal.add(10, 20, 30));
    System.out.println("10 + 20 + 30 + 40 =  " + cal.add(10, 20, 30, 40));
  }
  
}

Exemple de remplacement de méthodes : remplacementmethodes.png

Polymorphisme dynamique

Qu'est-ce que le polymorphisme dynamique ?

Le polymorphisme dynamique est un mécanisme qui permet de définir des méthodes avec le même nom, le même type de retour et les mêmes paramètres dans la classe de base et les classes dérivées.

L'appel à une méthode surchargée est résolu au moment de l'exécution.

Un exemple concret de polymorphisme dynamique peut être illustré avec la classe "Shape" : polyshape.png

// Shape Class
class Shape {
  
  public double getArea() {
    return 0;
  }
  
}
 
// A Rectangle is a Shape
class Rectangle extends Shape {  // extended form the Shape class
 
  private double length;
  private double width;
  
  public Rectangle(double length, double width) {
    this.length = length;
    this.width = width;
  }
 
  public double getArea() {
    return this.width * this.length;
  }
  
}
 
// A Circle is a Shape
class Circle extends Shape {
 
  private double radius;
  
  public Circle(double radius) {
    this.radius = radius;
  }
 
  public double getArea() {
    return 3.13 * this.radius * this.radius;
  }
 
 
  public static void main(String args[]) {
    Shape[] shape = new Shape[2]; // Creating the shape array of size 2
 
    shape[0] = new Circle(3); // creating the circle object at index 0
    shape[1] = new Rectangle(2, 3); // creating the rectangle object at index 1
 
    System.out.println("Area of Circle: " + shape[0].getArea());
    System.out.println("Area of Rectangle: " + shape[1].getArea());
  }
 
}

Une variable de référence de la classe de base peut être référencée aux objets des classes dérivées :

Shape obj1 = new Circle(3);
Shape obj2 = new Rectangle(2, 3);
 
//.
//.
//.
 
obj1.getArea();
obj2.getArea();
 

Ici, les variables de référence obj1et obj2sont de la Shapeclasse, mais elles pointent respectivement vers Circleet .Rectangle polyref.png

Explication obj1.getArea()exécutera getArea()la méthode de la Circleclasse de sous-classe.

obj2.getArea()exécutera getArea()la méthode de la Rectangleclasse de sous-classe.

obj1est une référence à la Circleclasse, elle appelle la méthode de Circlela classe, car elle pointe vers un objet Circle .

obj2est une référence à la Rectangleclasse, elle appelle la méthode de Rectanglela classe, car elle pointe vers un objet Rectangle .

Ceci est décidé pendant l'exécution et est donc appelé polymorphisme dynamique ou d'exécution .

Différence entre polymorphisme statique et dynamique

Types de polymorphisme

Il existe deux types de polymorphisme :

Le polymorphisme statique est également appelé polymorphisme au moment de la compilation.

Le polymorphisme dynamique est également appelé polymorphisme d'exécution deuxtypespolymor.png

Polymorphisme statique & polymorphisme dynamique

Polymorphisme statiquePolymorphisme dynamique
- Le polymorphisme résolu au moment de la compilation est appelé polymorphisme statique.
- La surcharge de méthode est utilisée dans le polymorphisme statique.
- Le polymorphisme résolu au cours de l'exécution est appelé polymorphisme dynamique.
- Le remplacement de méthode est utilisé dans le polymorphisme dynamique.

Exemple de polymorphisme statique

class Calculator {
 
  int add(int num1, int num2) {
    return num1 + num2;
  }
 
  int add(int num1, int num2, int num3) {
    return num1 + num2 + num3;
  }
 
  public static void main(String args[]) {
  
    Calculator obj = new Calculator();
    System.out.println("10 + 20 = " + obj.add(10, 20));
    System.out.println("10 + 20 + 30 = " + obj.add(10, 20, 30));
  }
  
}

Dans cet exemple, nous avons deux définitions de la méthode add() dans la classe Calculator. La méthode add() appelée est déterminée par la liste des paramètres lors de la compilation. C'est pourquoi cela est également connu sous le nom de polymorphisme au moment de la compilation.

Exemple de polymorphisme dynamique

// Shape Class
class Shape {
 
  public double getArea() {
    return 0;
  }
  
}
 
// A Rectangle is a Shape
class Rectangle extends Shape {   // extended form the Shape class
 
  private double length;
  private double width;
  
  public Rectangle(double length, double width) {
    this.length = length;
    this.width = width;
  }
 
  public double getArea() {
    return this.length * this.width;
  }
  
}
 
// A Circle is a Shape
class Circle extends Shape {
 
  private double radius;
  
  public Circle(double radius) {
    this.radius = radius;
  }
 
  public double getArea() {
    return this.radius * this.radius * 3.14;
  }
 
 
  public static void main(String args[]) {
    Shape[] shape = new Shape[2]; // Creating the shape array of size 2
 
    shape[0] = new Circle(2); // creating the circle object at index 0
    shape[1] = new Rectangle(2, 3); // creating the rectangle object at index 1
 
    System.out.println("Area of Circle: " + shape[0].getArea());
    System.out.println("Area of Rectangle: " + shape[1].getArea());
  }
 
}

Dans cet exemple, nous avons trois classes : Shape, Circle et Rectangle. Shape est la classe parent, tandis que Circle et Rectangle sont des classes enfant. Les classes enfant remplacent la méthode getArea() de la classe parent.

Nous créons des objets des classes enfant et les affectons à une référence de la classe parent. Ainsi, au moment de l'exécution, le type de l'objet est déterminé pour déterminer quelle méthode sera appelée. C'est ce qu'on appelle le polymorphisme d'exécution.

Classes abstraites et interfaces

Qu'est-ce que l'Abstraction ?

Définition

L'abstraction en programmation orientée objet se résume à montrer à l'utilisateur les caractéristiques essentielles d'un objet tout en cachant les détails internes. Cela permet de réduire la complexité en ne nécessitant de l'utilisateur que la connaissance de "ce que fait un objet" plutôt que de savoir "comment ça marche". classeabstract.png

Dans l'exemple donné, il est illustré de manière concrète l'abstraction dans le contexte d'une application avec des utilisateurs et un administrateur.

L'abstraction permet de limiter les fonctionnalités accessibles à un utilisateur, lequel interagit uniquement avec les fonctionnalités spécifiques de l'application, sans avoir connaissance des détails de sa mise en œuvre ou de son développement. L'utilisateur est principalement intéressé par la fonctionnalité de l'application.

D'autre part, l'administrateur dispose d'un accès plus étendu aux fonctionnalités de l'application et n'a rien de caché. Il peut surveiller l'activité des utilisateurs, connaître les détails du développement de l'application et mettre en place de nouvelles fonctionnalités en les déployant.

Dans cet exemple, l'abstraction est appliquée à l'utilisateur pour restreindre ses fonctionnalités, tandis que l'administrateur a un accès complet sans restriction.

abstractvolume.png

Dans un exemple concret d'abstraction, prenons le cas du bouton de volume sur une télécommande de télévision. En appuyant simplement sur ce bouton, nous demandons au téléviseur d'augmenter le volume. Le bouton appelle la fonction volumeUp(). Le téléviseur réagit en produisant un son plus fort. Nous n'avons pas besoin de connaître les détails internes du fonctionnement du téléviseur, nous utilisons simplement la fonctionnalité exposée pour interagir avec le volume.

En Java, nous pouvons également observer l'abstraction en action. Par exemple, la classe Math de Java offre de nombreuses méthodes intégrées qui facilitent la programmation. Nous pouvons utiliser ces méthodes dans notre code pour accéder à leur fonctionnalité intégrée.

class TestAbstraction {
 
  public static void main( String args[] ) {
    int min = Math.min(15,18);     //find min of two numbers
    double square = Math.pow(2,2); //calculate the power of a number
 
    System.out.println("The min of 15 & 18 is: " + min);
    System.out.println("The square of 2 is: " + square);
  }
 
}

Dans le code ci-dessus :

  • La Math.min()méthode est utilisée pour trouver le minimum des deux nombres
  • La Math.pow()méthode est utilisée pour trouver 2 à la puissance 2.

Mais l'utilisateur n'a pas besoin de connaître l'implémentation de ces deux méthodes dans la classe Math.

Types de données abstraites

Un type de données abstrait (ou classe abstraite) est un type qui définit uniquement "quelles opérations doivent être effectuées ?" plutôt que "comment elles doivent être exécutées ?".

En définissant des types de données abstraits, les utilisateurs ont seulement connaissance de l'essentiel, c'est-à-dire la fonctionnalité offerte par ces types de données, mais ils ne savent pas "comment l'implémentation doit être effectuée pour obtenir cette fonctionnalité spécifiée ?" Une partie de cette implémentation est cachée.

Un exemple de type de données abstrait peut être une classe abstraite de pile intégrée à Java, pour laquelle l'utilisateur sait qu'elle possède des méthodes comme push(), pop(), size(), etc., mais il ne sait pas comment ces méthodes sont implémentées en interne.

Comment réaliser l'abstraction

En Java, les éléments essentiels utilisés pour réaliser l'abstraction sont :

Les classes abstraites : Elles sont déclarées avec le mot-clé "abstract" et peuvent contenir des méthodes abstraites ( déclarées sans corps). Les classes abstraites servent de modèle pour les classes dérivées, mais elles ne peuvent pas être instanciées directement.

Les interfaces : Elles définissent un contrat comportant des méthodes abstraites et des constantes. Les interfaces permettent de spécifier un ensemble de fonctionnalités qu'une classe peut implémenter, indépendamment de sa hiérarchie de classes.

L'utilisation du mot-clé "abstract" est essentielle pour réaliser l'abstraction en Java. Lorsqu'il est utilisé avec des méthodes ou des classes, il spécifie uniquement "quelles opérations doivent être effectuées". Lorsqu'une méthode ou une classe est déclarée comme abstraite, les détails d'implémentation doivent être gérés par ceux qui utilisent cette méthode ou cette classe. Cela permet de définir l'abstraction en indiquant ce qui doit être fait, sans entrer dans les détails de comment cela doit être fait.

Classes abstraites et méthodes

Méthodes abstraites

Une méthode avec le mot-clé abstractdans sa déclaration est appelée méthode abstraite .

Règles à suivre

Une méthode abstraite en Java est une méthode qui n'a pas de corps ou de définition dans une classe abstraite ou une interface.

Les points essentiels à retenir sont les suivants :

  • Une méthode abstraite est déclarée uniquement à l'intérieur d'une classe abstraite ou d'une interface.
  • Une classe doit être déclarée comme abstraite pour contenir des méthodes abstraites, car les classes non abstraites ne peuvent pas avoir de méthodes abstraites.
  • Une méthode abstraite ne peut pas être déclarée comme privée, car elle doit être implémentée dans une autre classe.

En ce qui concerne la syntaxe, la déclaration générale d'une méthode abstraite est la suivante :

public abstract void methodName(parameter(s));

La déclaration d'une méthode abstraite a :

  1. Un identifiant d'accès
  2. Le mot cléabstract
  3. Un returntype
  4. Un nom de méthode
  5. Le(s) paramètre(s) à passer
  6. Un point-virgule ( ; ) pour terminer la déclaration

Classe abstraite

Une classe abstraite est une classe déclarée à l'aide du mot-clé abstract.

Règles à suivre

Les éléments essentiels concernant les classes abstraites sont les suivants :

  • Une classe abstraite ne peut pas être instanciée, ce qui signifie qu'on ne peut pas créer d'objet directement à partir d'une classe abstraite.

  • Une classe abstraite peut contenir la déclaration de méthode(s) abstraite(s), car le corps d'une méthode abstraite ne peut pas être implémenté dans une classe abstraite. Cependant, il n'est pas obligatoire d'avoir des méthodes abstraites dans une classe abstraite.

  • Les méthodes non abstraites (ou méthodes normales) peuvent être implémentées dans une classe abstraite, c'est-à-dire qu'elles peuvent avoir une implémentation concrète.

  • Pour utiliser une classe abstraite, il est nécessaire de créer une classe enfant (ou sous-classe) qui hérite de la classe abstraite.

  • La classe enfant qui hérite de la classe abstraite doit implémenter toutes les méthodes abstraites déclarées dans la classe abstraite mère.

  • Une classe abstraite peut contenir tous les autres éléments d'une classe Java normale, tels qu'un constructeur, des variables statiques et des méthodes statiques.

Ces éléments sont essentiels pour comprendre le concept et l'utilisation des classes abstraites en Java.

Déclaration:

abstract class ClassName {
  
  // Implementation here
  
}

Mise en œuvre

Les classes abstraites sont utilisées pour réaliser l'abstraction en Java.

Envisagez de modéliser un règne animal à l'aide de Java ayant :

  • Une abstractclasse de base nomméeAnimal
  • Une classe enfant nomméeDog
  • Une classe enfant nomméeCat
  • Une classe enfant nomméeSheep

Tous ces animaux font des sons différents : animal1.png animal2.png animal3.png

Dans l'exemple précédent, il est clair que la classe Animal représente les caractéristiques communes à tous les animaux, tandis que les classes enfant doivent mettre en œuvre les caractéristiques spécifiques à chaque type d'animal. Les classes abstraites offrent une fonctionnalité similaire en termes de conception. Voici une implémentation de cet exemple ci-dessous :

abstract class Animal {
 
public abstract void makeSound();
 
public void move() {
System.out.println(getClass().getSimpleName()+" is moving");
//getClass().getSimpleName() is an inbuilt functionality of Java
//to get the class name from which the method is being called
}
 
}
 
class Dog extends Animal {
 
    @Override
    public void makeSound() {
    System.out.println("Woof Woof...");
}
 
}
 
class Cat extends Animal {
 
    @Override
    public void makeSound() {
    System.out.println("Meow Meow...");
}
 
}
 
class Sheep extends Animal {
 
    @Override
    public void makeSound() {
    System.out.println("Baa Baa..");
}
 
}
 
class Main {
 
public static void main(String args[]) {
// Creating the objects
Animal dog = new Dog();  
Animal cat = new Cat();
Animal sheep = new Sheep();
 
    dog.makeSound();    // Calling methods from Dog
    dog.move();
 
    cat.makeSound();    // Calling methods from Cat
    cat.move();
 
    sheep.makeSound();  // Calling methods from Sheep
    sheep.move();
}
 
}

Une classe abstraite peut être bénéfique car :

  • Elle permet de définir des fonctionnalités communes à toutes les classes enfants. Par exemple, dans l'exemple donné, tous les animaux peuvent se déplacer, donc la méthode move() est implémentée dans la classe abstraite Animal, et les classes enfants peuvent l'utiliser sans avoir à l'implémenter individuellement.

  • Elle permet de déclarer des méthodes abstraites, qui doivent être implémentées dans les classes enfants. Dans l'exemple, une méthode abstraite est déclarée dans la classe Animal pour représenter le fait que tous les animaux émettent des sons différents. Les classes enfants doivent donc utiliser l'annotation @Override pour fournir leur propre implémentation de cette méthode.

Ainsi, en utilisant des classes abstraites, vous pouvez définir des comportements communs et des contraintes pour les classes enfants, tout en leur permettant de fournir des implémentations spécifiques à leurs propres caractéristiques.

Interfaces

Qu'est-ce qu'une interface ?

Une interface en Java spécifie le comportement qu'une classe doit implémenter.

Une interface permet d'atteindre une abstraction totale en déclarant les signatures des méthodes abstraites (ce qu'il faut faire) sans fournir les détails de leur implémentation (comment faire). Cela signifie que les interfaces satisfont à la définition de l'abstraction. Les classes qui implémentent une interface doivent fournir une implémentation pour toutes les méthodes déclarées dans cette interface.

Une interface peut être considérée comme un contrat que la classe doit respecter lors de son implémentation. En d'autres termes, la classe qui implémente une interface doit obligatoirement redéfinir toutes les méthodes abstraites déclarées dans cette interface.

Pour déclarer une interface en Java, on utilise le mot-clé "interface" de la même manière que pour déclarer une classe.

interface interfaceName {
  
  // Code goes here
  
}

Règles à suivre

Une interface peut avoir :

  • Des méthodes abstraites.
  • Des méthodes par défaut.
  • Des méthodes statiques.
  • Des méthodes privées.
  • Des méthodes privées et statiques.
  • Des variables publiques, statiques et finales.
  • Toutes les méthodes déclarées ou implémentées dans une interface sont automatiquement publiques, et toutes les variables sont automatiquement publiques, statiques et finales.

Une interface ne peut pas être instanciée, tout comme une classe abstraite.

Pour utiliser une interface, une classe doit implémenter toutes les méthodes abstraites déclarées dans l'interface.

Une interface ne peut pas contenir de constructeurs.

Une classe ne peut pas hériter de plusieurs classes, mais elle peut implémenter plusieurs interfaces.

Une interface peut hériter d'une autre interface.

Une interface ne peut pas être déclarée comme privée ou protégée.

Remarque : Une classe utilise le mot-clé implementspour utiliser une interface, mais une interface utilise le mot-clé extendspour utiliser une autre interface. interfaces.png

Mise en œuvre

Voyons les interfaces en action en utilisant l'exemple ci-dessous :

  • Une classe de base nomméeBird
  • Une classe dérivée nomméeParrot
  • Une classe dérivée nomméePenguin
  • Une interface nomméeCanFly

interface1.png interface2.png interfaces3.png

le code :

// Base class Bird
class Bird {   
 
  // Common trait of all birds so implemented in the base class
  public void eat() { 
    System.out.println(getClass().getSimpleName() + " is eating!");
  }
  
}// End of Bird class
 
interface CanFly {
 
  // The method is by default abstract and public
  void flying();   
  
}// End of CanFly interface
 
class Parrot extends Bird implements CanFly { // Parrot is extending from Bird and implementing CanFly
 
  @Override               // If you don't implement the flying() you will get an error!
  public void flying() { // Overriding the method of CanFly interface
    System.out.println("Parrot is Flying!");
  }
} // End of Parrot class
 
class Penguin extends Bird { // Penguin is a bird so extending from Bird
  
  // Penguin cannot fly so not implementing CanFly
  public void walk() {
    System.out.println("Penguin is Walking!");
  }
  
} // End of Penguin class
 
class Main {
 
  public static void main(String[] args) {
 
    Parrot parrot = new Parrot();   // Creating the Parrot object
    Penguin penguin = new Penguin(); // Creating the Penguin object
 
    parrot.eat();
    parrot.flying();
 
    System.out.println();    // Just creating a newline on console
 
    penguin.eat();
    penguin.walk();
    
  } // End of main()
  
} // End of Main class

Avantages des interfaces

Les interfaces en Java permettent d'atteindre une abstraction complète.

Elles favorisent un couplage lâche dans une application, ce qui signifie qu'un changement dans une classe n'affecte pas l'implémentation d'une autre classe.

Les interfaces permettent de décomposer des conceptions complexes et de réduire les dépendances entre les objets.

Elles offrent la possibilité d'obtenir un héritage multiple.

Héritage multiple

Qu'est-ce que l'héritage multiple ?

Lorsqu'une classe est dérivée de plusieurs classes de base, c'est-à-dire lorsqu'une classe a plusieurs classes parentes immédiates, il s'agit d'une instance de Multiple Inheritance . Exemple:

UNE Hyundai Elantra EST UNE Car Une Hyundai Elantra EST UN Sedan aussi heritagemultiplllle.png

En Java, pour implémenter l'héritage multiple, on utilise les interfaces. Une classe ne peut pas hériter de plusieurs classes, mais elle peut implémenter plusieurs interfaces.

L'implémentation de l'héritage multiple peut être réalisée de la manière suivante :

Une classe de base appelée "Car" Une interface appelée "IsSedan" Une classe dérivée "ElantraCar" qui implémente l'interface "IsSedan" Ainsi, en utilisant l'interface, nous pouvons simuler l'héritage multiple en Java.

Cet exemple illustre comment mettre en œuvre l'exemple de l'Elantra mentionné précédemment, en utilisant une classe de base "Car", une interface "IsSedan" et une classe dérivée "ElantraCar" qui implémente l'interface.

Cela permet d'obtenir une structure similaire à l'illustration mentionnée précédemment. heritageimplements.png

le code :

class Car {  // Base class
 
  private int model;  // Common features of all cars
  private String manufacturer;
 
  public Car(int model, String manufacturer) {  // Constructor
    this.model = model;
    this.manufacturer = manufacturer;
  }
 
  public void printDetails() {
 
    System.out.println("The model of " + getClass().getSimpleName() + " is: " + model);
    System.out.println("The manufacturer of " + getClass().getSimpleName() + " is: " + manufacturer);
  }
 
}  // End of Car class
 
interface IsSedan {  // Interface for sedans
 
  int bootSpace = 420;  // Sedans have boot space
 
  void bootSpace();    // Every sedan must implement this
 
}  // End of IsSedan interface
 
class Elantra extends Car implements IsSedan {  // Elantra is a Car and is a Sedan also
 
  private String variant;    // Elantra's data member
 
  public Elantra(int model, String variant) {  // Constructor
    super(model, "Hyundai");  // Calling the parent constructor with alredy known manufacturer
    this.variant = variant;  
  }
 
  @Override
  public void bootSpace() { // Implementation of the interface method
    System.out.println("The bootspace of Elantra is: " + IsSedan.bootSpace +" litres");
  }
 
  @Override
  public void printDetails() {  // Overriding the parent class's inherited method
    super.printDetails();    // Calling the method from parent class
    System.out.println("The variant of Elantra is: " + variant); // printing the data member of this class
  }
  
}  // End of Elantra class
 
 
class Main {
 
  public static void main(String[] args) {
 
    Elantra sport = new Elantra(2019, "Sport");  //creating Sports variant Elantra
    Elantra eco = new Elantra(2018, "Eco");      //creating Eco variant Elantra
 
    sport.printDetails();    
    sport.bootSpace();       
 
    System.out.println();
 
    eco.printDetails();
    eco.bootSpace();
  }
  
}

Interface vs classe abstraite

Les interfaces et les classes abstraites sont toutes deux utilisées pour réaliser l'abstraction, mais avec certaines des principales différences :

InterfacesClasses abstraites
- Prise en charge de l'héritage multiple
- Tous les membres sont public
- Tous les membres de données sont staticetfinal
- Impossible d'avoir des constructeurs
- Ne prend pas en charge l'héritage multiple
- Peut avoir private, protectedet publicmembres
- Peut également avoir des membres non statiques et non finaux
- Les constructeurs peuvent être définis

Composition, agrégation et association

Interaction entre les objets de classe

Nous avons maintenant une compréhension globale de la définition et du comportement d'une classe. Les concepts d'héritage et de polymorphisme nous permettent de créer des classes dérivées à partir d'une classe de base. L'héritage établit une relation entre les classes, mais il existe également des situations où des relations existent entre les objets eux-mêmes.

La prochaine étape consiste à utiliser différents objets de classe pour concevoir une application. Cela signifie que les objets de classes indépendantes doivent trouver un moyen d'interagir entre eux. compo.png

Relations entre les classes

Dans la programmation orientée objet, il existe trois relations de classe couramment utilisées. Nous avons déjà étudié la relation IS-A dans le chapitre sur l'héritage. Les deux autres relations sont les suivantes :

Partie de : Dans cette relation, un objet d'une classe est un composant d'une autre classe. Si deux classes, A et B, sont dans une relation "partie de", cela signifie que l'objet de la classe A fait partie de la classe B, ou vice versa.

Les instances des classes composantes ne peuvent être créées qu'à l'intérieur de la classe conteneur. Par exemple, dans l'exemple donné, la classe B et la classe C ont leurs propres implémentations, mais les objets de ces classes font partie de l'implémentation de la classe A et sont créés uniquement lorsque un objet de classe A est créé. Ainsi, "partie de" est une relation de dépendance entre les classes.

Il est important de comprendre ces relations de classe pour bien structurer et concevoir vos programmes orientés objet. partieDe.png

La relation "a un" est une relation moins étroite entre deux classes. La classe A et la classe B ont une relation "a un" si l'une ou les deux ont besoin de l'objet de l'autre pour effectuer une opération, mais les deux objets de classe peuvent exister indépendamment l'un de l'autre.

Cela signifie qu'une classe possède une référence à un objet de l'autre classe, mais n'a pas le contrôle sur la durée de vie de l'objet référencé de l'autre classe. aun.png

En programmation orientée objet, l'association est un terme générique utilisé pour décrire les relations entre objets, qu'il s'agisse de relations "has-a" (a un) ou "part-of" (partie de), ainsi que d'autres types de relations. L'association indique simplement qu'il existe une relation entre deux objets, sans préciser la nature de cette relation.

Dans les leçons à venir, nous nous concentrerons sur des formes spécifiques d'associations : l'agrégation et la composition. Ces formes d'association fournissent des structures et des comportements particuliers pour modéliser des relations plus spécifiques entre les objets.

Agrégation

L'agrégation suit le modèle "a-un" (has-A) en créant une relation parent-enfant entre deux classes, où une classe possède un objet de l'autre classe.

Ce qui rend l'agrégation unique, c'est que la durée de vie de l'objet possédé n'est pas liée à celle du propriétaire.

L'objet propriétaire peut être supprimé, mais l'objet possédé peut continuer à exister dans le programme. Dans l'agrégation, le parent ne contient qu'une référence à l'enfant, ce qui élimine la dépendance de l'enfant à l'égard du parent.

agregation.png

Pour implémenter l'agrégation, nous avons besoin de références d'objet.

Prenons l'exemple des personnes et de leur pays d'origine. Chaque personne est associée à un pays, mais le pays peut exister indépendamment de cette personne.

class Country {
  
    private String name;
    private int population;
 
    public Country(String n, int p) {
      name = n;
      population = p;
    }
    public String getName() {
      return name;
    }
  
}
 
class Person {
  
    private String name;
    private Country country; // An instance of Country class
 
    public Person(String n, Country c) {
      name = n;
      country = c;
    }
 
    public void printDetails() {
      System.out.println("Name: " + name);
      System.out.println("Country: " + country.getName());
    }
  
}
 
class Main {
  
  public static void main(String args[]) {
    Country country = new Country("Utopia", 1);
    {
      Person user = new Person("Darth Vader", country);
      user.printDetails();
    }
    // The user object's lifetime is over
 
    System.out.println(country.getName()); // The country object still exists!
  }
  
}

Comme il est évident, l'objet country continue d'exister même après la sortie de la portée où il a été créé. Cela crée une relation faiblement couplée entre les deux classes.

Composition

La composition est une pratique qui consiste à créer des objets d'une autre classe à l'intérieur de votre classe. Dans ce scénario, la classe qui crée l'objet de l'autre classe est appelée le propriétaire et est responsable de la durée de vie de cet objet.

Les relations de composition sont des relations de type "partie de" où la partie doit faire partie de l'objet entier. On peut réaliser une composition en ajoutant de plus petites parties d'autres classes pour créer une entité complexe.

Ce qui rend la composition unique, c'est que la durée de vie de l'objet possédé dépend de la durée de vie du propriétaire.

Par exemple, une voiture est composée d'un moteur, de pneus et de portes. Dans ce cas, la voiture possède ces objets, donc la voiture est la classe propriétaire, et les pneus, les portes et le moteur sont les classes possédées.

compositionLambo.png

class Engine {
  
  private int capacity;
  
  public Engine(){
    capacity = 0;
  }
  
  public Engine(int cap) {
    capacity = cap;
  }
  
  public void engineDetails() {
    System.out.println("Engine details: " + capacity);
  }
  
}
 
class Tires {
  
  private int noOfTires;
  
  public Tires() {
    noOfTires = 0;
  }
  
  public Tires(int nt) {
    noOfTires = nt;
  }
  
  public void tireDetails() {
    System.out.println("Number of tyres: " +  noOfTires);
  }
  
}
 
class Doors {
  
  private int noOfDoors;
  
  public Doors() {
    noOfDoors = 0;
  }
  
  public Doors(int nod) {
    noOfDoors = nod;
  }
  
  public void doorDetails() {
    System.out.println("Number of Doors: " + noOfDoors);
  }
  
}
 
class Car {
  
  private Engine eObj;
  private Tires tObj;
  private Doors dObj;
  private String color;
  
  public Car(String col, int cap, int nt, int nod) {
    this.eObj = new Engine(cap);;
    this.tObj = new Tires(nt);;
    this.dObj = new Doors(nod);
    
    color = col;    
  }
  
  public void carDetail() {
    eObj.engineDetails();
    tObj.tireDetails();
    dObj.doorDetails();
    System.out.println("Car color: " + color);
  }
  
}
  
class Main {
  
  public static void main(String[] args) {
    Car cObj = new Car("Black", 1600, 4, 4);
    cObj.carDetail();
  }
}

Nous avons créé une classe appelée Car qui contient des objets des classes Engine, Tires et Doors. La classe Car est responsable de la gestion de la durée de vie de ces objets. Cela signifie que lorsque la voiture est détruite, les objets Tires, Engine et Doors le sont également.

Last updated on June 21, 2024