OOP en Java : Classes, objets, encapsulation, héritage et abstraction
Apprenez la programmation orientée objet en Java avec des exemples pratiques. Maîtriser les classes, les objets, l'héritage, l'encapsulation et les classes abstraites à l'aide d'un système de menu de restaurant.
Java est régulièrement l'un des trois langages les plus populaires au monde. Son adoption dans des domaines tels que le développement de logiciels d'entreprise, les applications mobiles Android et les applications web à grande échelle est inégalée. Son système de type solide, son écosystème étendu et sa capacité à "écrire une fois, exécuter n'importe où" le rendent particulièrement attrayant pour la construction de systèmes robustes et évolutifs. Dans cet article, nous verrons comment les fonctions de programmation orientée objet de Java permettent aux développeurs d'exploiter efficacement ces capacités, en leur permettant de créer des applications maintenables et évolutives grâce à une organisation et une réutilisation appropriées du code.
Note sur l'organisation et l'exécution du code Java
Avant de commencer à écrire du code, procédons à quelques réglages.
Comme pour sa syntaxe, Java a des règles strictes en matière d'organisation du code.
Tout d'abord, chaque classe publique doit se trouver dans son propre fichier, nommé exactement comme la classe mais avec une extension.java. Ainsi, si je veux écrire une classe d'ordinateur portable , le nom de fichier doit être Laptop.java-en respectant la casse. Vous pouvez avoir des classes non publiques dans le même fichier, mais il est préférable de les séparer. Je sais que nous prenons de l'avance - nous parlons de l'organisation des cours avant même de les écrire - mais il est bon d'avoir une idée approximative de l'endroit où placer les choses à l'avance.
Tous les projets Java doivent comporter un fichier Main.java avec la classeMain. C'est ici que vous testez vos classes en créant des objets à partir d'elles.
Pour exécuter du code Java, nous utiliseronsIntelliJ IDEAun IDE Java très répandu. Après avoir installé IntelliJ :
Créez un nouveau projet Java (Fichier > Nouveau > Projet).
Cliquez avec le bouton droit de la souris sur le dossier src pour créer le fichier Main.java et collez le contenu suivant :
publicclassMain{publicstaticvoidmain(String[] args){// Create and tests objects here}}Propulsé par
Lorsque nous parlons de classes, nous écrivons du code dans d'autres fichiers que le fichierMain.java. Mais s'il s'agit de créer et de tester desobjets, nous passons à Main.java.
Pour exécuter le programme, vous pouvez cliquer sur le bouton vert de lecture situé à côté de la méthode principale :
La sortie sera affichée dans la fenêtre de l'outil Exécuter en bas.
Si vous êtes totalement novice en matière de Java, veuillez consulter notre cours d'introduction à Java.Cours d'introduction à Javaqui couvre les principes fondamentaux des types de données et du flux de contrôle Java avant de poursuivre.
Sinon, plongeons dans le vif du sujet.
Classes et objets Java
Qu'est-ce qu'une classe, exactement ?
Les classes sont des constructions de programmation en Java qui permettent de représenter des concepts du monde réel. Par exemple, considérez cette classeMenuItem (créez un fichier pour écrire cette classe dans votre IDE) :
publicclassMenuItem{publicString name;publicdouble price;}Propulsé par
La classe nous donne un plan ou un modèle pour représenter les différents éléments du menu d'un restaurant. En modifiant les deux attributs de la classe, name , et price, nous pouvons créer d'innombrables objets de menu comme un hamburger ou une salade.
Ainsi, pour créer une classe en Java, vous commencez par une ligne décrivant le niveau d'accès de la classe (private,public, ou protected) suivie du nom de la classe. Immédiatement après la mise entre parenthèses, vous décrivez les attributs de votre classe.
Mais comment créer des objets appartenant à cette classe ? Java permet cela grâce à des méthodes de construction :
Un constructeur est une méthode spéciale qui est appelée lorsque nous créons un nouvel objet à partir d'une classe. Il initialise les attributs de l'objet avec les valeurs que nous avons fournies. Dans l'exemple ci-dessus, le constructeur prend un nom et un prix en paramètre et les affecte aux champs de l'objet en utilisant le mot-clé "this" pour faire référence à une future instance de l'objet.
La syntaxe du constructeur est différente de celle des autres méthodes de classe car elle ne nécessite pas de spécifier un type de retour. En outre, le constructeur doit porter le même nom que la classe et doit avoir le même nombre d'attributs que celui que vous avez déclaré après la définition de la classe. Ci-dessus, le constructeur crée deux attributs parce que nous en avons déclaré deux après la définition de la classe : name et price.
Après avoir écrit votre classe et son constructeur, vous pouvez créer des instances (objets) dans votre méthode principale :
Ci-dessus, nous créons deux objets MenuItem dans les variables burger et salad. Comme l'exige Java, le type de la variable doit être déclaré, à savoir MenuItem. Ensuite, pour créer une instance de notre classe, nous écrivons le mot-clénew suivi de l'invocation de la méthode du constructeur.
Outre le constructeur, vous pouvez créer des méthodes ordinaires qui confèrent un comportement à votre classe. Par exemple, ci-dessous, nous ajoutons une méthode pour calculer le prix total après taxes :
publicclassMenuItem{publicString name;publicdouble price;// ConstructorpublicMenuItem(String name,double price){this.name = name;this.price = price;}// Method to calculate price after taxpublicdoublegetPriceAfterTax(){double taxRate =0.08;// 8% tax ratereturn price +(price * taxRate);}}Propulsé par
Nous pouvons maintenant calculer le prix total, taxes comprises :
publicclassMain{publicstaticvoidmain(String[] args){MenuItem burger =newMenuItem("Burger",3.5);System.out.println("Price after tax: $"+ burger.getPriceAfterTax());}}Propulsé par
L'objectif des classes est de fournir un schéma directeur pour la création d'objets. Ces objets seront ensuite utilisés par d'autres scripts ou programmes. Par exemple, nos objetsMenuItem peuvent être utilisés par une interface utilisateur qui affiche leur nom, leur prix et leur image à l'écran.
C'est pourquoi nous devons concevoir nos classes de manière à ce que leurs instances ne puissent être utilisées que de la manière que nous avons prévue. Pour l'instant, notre classe MenuItem est très basique et sujette aux erreurs. Une personne peut créer des objets aux attributs ridicules, comme une tarte aux pommes à prix réduit ou un sandwich à un million de dollars :
// Inside Main.javaMenuItem applePie =newMenuItem("Apple Pie",-5.99);// Negative price!MenuItem sandwich =newMenuItem("Sandwich",1000000);// Unreasonably expensiveSystem.out.println("Apple pie price: $"+ applePie.price);System.out.println("Sandwich price: $"+ sandwich.price);Propulsé par
Ainsi, la première chose à faire après avoir écrit une classe est de protéger ses attributs en limitant la façon dont ils sont créés et accédés. Pour commencer, nous voulons n'autoriser que des valeurs positives pour leprix et fixer une valeur maximale pour éviter d'afficher accidentellement des articles ridiculement chers.
Java nous permet d'accomplir cette tâche en utilisant des méthodes de type "setter" :
publicclassMenuItem{privateString name;privatedouble price;privatestaticfinaldoubleMAX_PRICE=100.0;publicMenuItem(String name,double price){this.name = name;setPrice(price);}publicvoidsetPrice(double price){if(price <0){thrownewIllegalArgumentException("Price cannot be negative");}if(price >MAX_PRICE){thrownewIllegalArgumentException("Price cannot exceed $"+MAX_PRICE);}this.price = price;}}Propulsé par
Examinons les nouveautés du bloc de code ci-dessus :
1. Nous avons rendu les attributs privés en ajoutant le mot-clé private. Cela signifie qu'ils ne sont accessibles qu'à l'intérieur de la classe MenuItem. L'encapsulation commence par cette étape cruciale.
2. Nous avons ajouté une nouvelle constante:MAX_PRICE:
privé (accessible uniquement au sein de la classe)
statique (partagé par toutes les instances)
final (ne peut être modifié après l'initialisation)
fixé à 100,0 $ comme prix maximum raisonnable
3. Nous avons ajouté une méthodesetPrice() qui :
Prend un paramètre de prix
Valide que le prix n'est pas négatif
Valide que le prix ne dépasse pas MAX_PRICE
Lance une exception IllegalArgumentException avec des messages descriptifs en cas d'échec de la validation.
Le prix n'est fixé que si toutes les validations sont réussies
4. Nous avons modifié le constructeur pour utiliser setPrice() au lieu d'assigner directement le prix. Cela permet de s'assurer que la validation des prix a lieu lors de la création de l'objet.
Nous venons de mettre en œuvre l'un des principaux piliers d'une bonne conception orientée objet - l'encapsulation. Ce paradigme permet de masquer les données et de contrôler l'accès aux attributs des objets, en garantissant que les détails de l'implémentation interne sont protégés des interférences externes et ne peuvent être modifiés qu'au moyen d'interfaces bien définies.
Faisons valoir notre point de vue en appliquant l'encapsulation à l'attributname. Imaginez un café qui ne sert que des lattes, des cappuccinos, des espressos, des americanos et des mokas.
Ainsi, les noms de nos éléments de menu ne peuvent être que l'un des éléments de cette liste. Voici comment nous pouvons appliquer cette règle dans le code :
// Rest of the class here...privatestaticfinalString[]VALID_NAMES={"latte","cappuccino","espresso","americano","mocha"};privateString name;publicvoidsetName(String name){String lowercaseName = name.toLowerCase();for(String validName :VALID_NAMES){if(validName.equals(lowercaseName)){this.name = name;return;}}thrownewIllegalArgumentException("Invalid drink name. Must be one of: "+String.join(", ",VALID_NAMES));}Propulsé par
Le code ci-dessus met en œuvre la validation des noms pour les éléments du menu d'un café. Voyons cela en détail :
1. Tout d'abord, il définit un tableau final statique privé VALID_NAMES qui contient les seuls noms de boissons autorisés : latte, cappuccino, espresso, americano et mocha. Ce tableau étant :
privé : accessible uniquement au sein de la classe
statique : partagé par toutes les instances
final : ne peut être modifié après l'initialisation
2. Il déclare un champ privé String name pour stocker le nom de la boisson.
3. La méthode setName() met en œuvre la logique de validation :
Prend en paramètre un nom de type chaîne
Le convertit en minuscules pour rendre la comparaison insensible à la casse.
Boucle sur le tableau VALID_NAMES
Si une correspondance est trouvée, le nom est défini et le message est renvoyé.
Si aucune correspondance n'est trouvée, une exception de type IllegalArgumentException est levée avec un message descriptif énumérant toutes les options valides.
Voici la classe complète jusqu'à présent :
publicclassMenuItem{privateString name;privatedouble price;privatestaticfinalString[]VALID_NAMES={"latte","cappuccino","espresso","americano","mocha"};privatestaticfinaldoubleMAX_PRICE=100.0;publicMenuItem(String name,double price){setName(name);setPrice(price);}publicvoidsetName(String name){String lowercaseName = name.toLowerCase();for(String validName :VALID_NAMES){if(validName.equals(lowercaseName)){this.name = name;return;}}thrownewIllegalArgumentException("Invalid drink name. Must be one of: "+String.join(", ",VALID_NAMES));}publicvoidsetPrice(double price){if(price <0){thrownewIllegalArgumentException("Price cannot be negative");}if(price >MAX_PRICE){thrownewIllegalArgumentException("Price cannot exceed $"+MAX_PRICE);}this.price = price;}}Propulsé par
Après avoir protégé la façon dont les attributs sont créés, nous voulons également protéger la façon dont on y accède. Pour ce faire, vous pouvez utiliser des méthodes de type "getter" :
publicclassMenuItem{// Rest of the code here...publicStringgetName(){return name;}publicdoublegetPrice(){return price;}}Propulsé par
Les méthodes Getter fournissent un accès contrôlé aux attributs privés d'une classe. Ils résolvent le problème de l'accès direct aux attributs, qui peut entraîner des modifications non souhaitées et rompre l'encapsulation.
Par exemple, sans getters, nous pourrions accéder directement aux attributs :
MenuItem item =newMenuItem("Latte",4.99);String name = item.name;// Direct access to attribute
item.name ="INVALID";// Can modify directly, bypassing validationPropulsé par
Avec les getters, nous assurons un accès approprié :
MenuItem item =newMenuItem("Latte",4.99);String name = item.getName();// Controlled access through getter// item.name = "INVALID"; // Not allowed - must use setName() which validatesPropulsé par
Cette encapsulation :
Protège l'intégrité des données en empêchant les modifications non valides
Permet de modifier l'implémentation interne sans affecter le code qui utilise la classe
Fournit un point d'accès unique qui peut inclure une logique supplémentaire si nécessaire
rend le code plus facile à maintenir et moins sujet aux bogues
Héritage
Notre classe commence à avoir fière allure, mais elle présente de nombreux problèmes. Par exemple, pour un grand restaurant servant de nombreux types de plats et de boissons, la classe n'est pas assez flexible.
Si nous voulons ajouter différents types de produits alimentaires, nous nous heurterons à plusieurs difficultés. Certains plats peuvent être préparés pour être emportés, tandis que d'autres doivent être consommés immédiatement. Les prix et les remises peuvent varier d'un produit à l'autre. Les plats peuvent nécessiter un suivi de la température ou un stockage spécial. Les boissons peuvent être chaudes ou froides avec des ingrédients personnalisables. Les articles peuvent nécessiter des informations sur les allergènes et des options de portions. Le système actuel ne permet pas de répondre à ces différentes exigences.
L'héritage offre une solution élégante à tous ces problèmes. Il nous permet de créer des versions spécialisées d'éléments de menu en définissant une classe de base MenuItem avec des attributs communs, puis en créant des classes enfantines qui héritent de ces éléments de base tout en ajoutant des caractéristiques uniques.
Par exemple, nous pourrions avoir une classe Drink pour les boissons avec des options de température, une classe Food pour les articles nécessitant une consommation immédiate et une classe Dessert pour les articles ayant des besoins de stockage particuliers - toutes ces classes héritant de la fonctionnalité principale des articles de menu.
Extension des classes
Mettons en œuvre ces idées en commençant par Drink:
Pour définir une classe enfant qui hérite d'une classe parent, nous utilisons le mot-cléextends après le nom de la classe enfant, suivi de celui de la classe parent. Après la définition de la classe, nous définissons les nouveaux attributs de cet enfant et implémentons son constructeur.
Mais remarquez que nous devons répéter l'initialisation de name et price avec isCold. Ce n'est pas idéal car la classe mère peut avoir des centaines d'attributs. En outre, le code ci-dessus provoquera une erreur lorsque vous le compilerez, car ce n'est pas la bonne façon d'initialiser les attributs de la classe parente. La bonne méthode consiste à utiliser le mot-clé super:
Le mot-clésuper est utilisé pour appeler le constructeur de la classe mère. Dans ce cas, super(name, price) appelle le constructeur deMenuItem pour initialiser ces attributs, évitant ainsi la duplication du code. Il suffit d'initialiser le nouvel attribut isCold spécifique à la classeDrink.
Ce mot-clé est très flexible car vous pouvez l'utiliser pour faire référence à la classe mère dans n'importe quelle partie de la classe enfant. Par exemple, pour appeler une méthode parent, vous utilisez super.methodName(), tandis que super.attributeName est destiné aux attributs.
Remise en cause de la méthode
Supposons maintenant que nous voulions ajouter une nouvelle méthode à nos classes pour calculer le prix total après taxes. Étant donné que les taux d'imposition peuvent varier d'un article de menu à l'autre (par exemple, aliments préparés ou boissons emballées), nous pouvons utiliser la méthode de superposition pour mettre en œuvre des calculs d'impôts spécifiques dans chaque classe enfant tout en conservant un nom de méthode commun dans la classe mère.
Voici à quoi cela ressemble :
publicclassMenuItem{// Rest of the MenuItem classpublicdoublecalculateTotalPrice(){// Default tax rate of 10%return price *1.10;}}publicclassFoodextendsMenuItem{privateboolean isVegetarian;publicFood(String name,double price,boolean isVegetarian){super(name, price);this.isVegetarian = isVegetarian;}@OverridepublicdoublecalculateTotalPrice(){// Food has 15% taxreturnsuper.getPrice()*1.15;}}publicclassDrinkextendsMenuItem{privateboolean isCold;publicDrink(String name,double price,boolean isCold){super(name, price);this.isCold = isCold;}@OverridepublicdoublecalculateTotalPrice(){// Drinks have 8% taxreturnsuper.getPrice()*1.08;}}Propulsé par
Dans cet exemple, la superposition de méthodes permet à chaque sous-classe de fournir sa propre implémentation de la méthode calculateTotalPrice():
La classe de base MenuItem définit un calcul de taxe par défaut de 10 %.
Quand Food et Drink étendent MenuItemils surchargent cette méthode pour mettre en œuvre leurs propres taux d'imposition :
Les produits alimentaires sont soumis à un taux d'imposition plus élevé de 15 %.
Les boissons bénéficient d'un taux d'imposition inférieur de 8 %.
L'annotation@Override est utilisée pour indiquer explicitement que ces méthodes remplacent la méthode de la classe mère. Cela permet de détecter les erreurs si la signature de la méthode ne correspond pas à celle de la classe mère.
Chaque sous-classe peut toujours accéder au prix de la classe parente en utilisant super.getPrice()démontrant ainsi que les méthodes surchargées peuvent utiliser les fonctionnalités de la classe parente tout en ajoutant leur propre comportement.
En bref, la superposition de méthodes fait partie intégrante de l'héritage et permet aux sous-classes de fournir leur propre implémentation des méthodes définies dans la classe mère, ce qui permet d'obtenir un comportement plus spécifique tout en conservant la même signature de méthode.
Classes abstraites
Notre hiérarchie de classes MenuItem fonctionne, mais il y a un problème : n'importe qui devrait pouvoir créer un simple objetMenuItem? Après tout, dans notre restaurant, chaque élément du menu est soit un plat, soit une boisson - il n'existe pas d'élément de menu générique.
Nous pouvons éviter cela en faisant de MenuItem une classe abstraite. Une classe abstraite ne fournit qu'un modèle de base - elle ne peut être utilisée que comme classe parente pour l'héritage, et ne peut pas être instanciée directement.
Pour rendre MenuItem abstrait, nous ajoutons le mot-clé abstract après son modificateur d'accès :
publicabstractclassMenuItem{privateString name;privatedouble price;publicMenuItem(String name,double price){setName(name);setPrice(price);}// Existing getters/setters remain the same// Make this method abstract - every subclass MUST implement itpublicabstractdoublecalculateTotalPrice();}Propulsé par
Les classes abstraites peuvent également avoir des méthodes abstraites comme calculateTotalPrice() ci-dessus. Ces méthodes abstraites servent de contrats qui obligent les sous-classes à fournir leurs implémentations. En d'autres termes, toute méthode abstraite d'une classe abstraite doit être implémentée par les classes enfantines.
Réécrivons donc Food et Drink en tenant compte de ces changements :
Grâce à la mise en œuvre de ce système de menus, nous avons vu comment l'abstraction et l'héritage fonctionnent ensemble pour créer un code flexible et facile à maintenir qui peut facilement s'adapter aux différentes exigences de l'entreprise.
Conclusion
Aujourd'hui, nous avons eu un aperçu de ce dont Java est capable en tant que langage de programmation orienté objet. Nous avons couvert les bases des classes, des objets et quelques piliers de la POO : encapsulation, héritage et abstraction à travers un système de menu de restaurant.
Pour que ce système soit prêt pour la production, il vous reste encore beaucoup de choses à apprendre, comme les interfaces (qui font partie de l'abstraction), le polymorphisme et les modèles de conception OOP. Pour en savoir plus sur ces concepts, reportez-vous à notreIntroduction à la POO en Java.
Si vous souhaitez tester vos connaissances en Java, essayez de répondre à certaines des questions de notre article sur les questions d'entretien en Java. notre article Questions d'entretien sur Java.
FAQ sur la POO en Java
Quels sont les pré-requis pour suivre ce tutoriel Java OOP ?⌄
Vous devez avoir des connaissances de base en programmation Java, notamment en ce qui concerne les types de données, les variables, le flux de contrôle (if/else, boucles) et la syntaxe de base. Notre cours Introduction à Java couvre ces fondamentaux si vous avez besoin d'une remise à niveau.
Pourquoi utiliser un système de menu de restaurant pour expliquer les concepts de la POO ?⌄
Le système de menu d'un restaurant est un exemple intuitif qui démontre les applications réelles des principes de la POO. Il montre naturellement l'héritage (différents types d'articles de menu), l'encapsulation (protection des valeurs de prix et de nom) et les interfaces (programmes de fidélisation, contrôle de la température). La plupart des gens comprennent le fonctionnement des restaurants, ce qui facilite la compréhension des concepts.
Quelle est la différence entre les classes abstraites et les interfaces en Java ?⌄
Une classe ne peut étendre qu'une seule classe abstraite mais mettre en œuvre plusieurs interfaces.
Les classes abstraites peuvent avoir des champs et des constructeurs, ce qui n'est pas le cas des interfaces.
Les classes abstraites peuvent avoir des méthodes abstraites et concrètes ; les interfaces n'ont traditionnellement que des méthodes abstraites (avant Java 8).
Les classes abstraites fournissent une implémentation de base, tandis que les interfaces définissent un contrat.
Apprenez l'essentiel de l'architecture d'un entrepôt de données, des composants clés aux meilleures pratiques, pour construire un système de données évolutif et efficace !
Découvrez les classes abstraites Python, leur utilité et comment utiliser le module `abc` pour garantir la cohérence des interfaces. Comprend des exemples pratiques et des bonnes pratiques pour une mise en œuvre efficace.
Initiez-vous aux structures de données de Python : apprenez-en plus sur les types de données et les structures de données primitives et non primitives, telles que les chaînes de caractères, les listes, les piles, etc.
Apprenez à utiliser JSON en Python, notamment la sérialisation, la désérialisation, le formatage, l'optimisation des performances, la gestion des API, ainsi que les limites et les alternatives de JSON.
Java est régulièrement l'un des trois langages les plus populaires au monde. Son adoption dans des domaines tels que le développement de logiciels d'entreprise, les applications mobiles Android et les applications web à grande échelle est inégalée. Son système de type solide, son écosystème étendu et sa capacité à "écrire une fois, exécuter n'importe où" le rendent particulièrement attrayant pour la construction de systèmes robustes et évolutifs. Dans cet article, nous verrons comment les fonctions de programmation orientée objet de Java permettent aux développeurs d'exploiter efficacement ces capacités, en leur permettant de créer des applications maintenables et évolutives grâce à une organisation et une réutilisation appropriées du code.
Note sur l'organisation et l'exécution du code Java
Avant de commencer à écrire du code, procédons à quelques réglages.
Comme pour sa syntaxe, Java a des règles strictes en matière d'organisation du code.
Tout d'abord, chaque classe publique doit se trouver dans son propre fichier, nommé exactement comme la classe mais avec une extension
.java. Ainsi, si je veux écrire une classe d'ordinateur portable , le nom de fichier doit êtreLaptop.java-en respectant la casse. Vous pouvez avoir des classes non publiques dans le même fichier, mais il est préférable de les séparer. Je sais que nous prenons de l'avance - nous parlons de l'organisation des cours avant même de les écrire - mais il est bon d'avoir une idée approximative de l'endroit où placer les choses à l'avance.Tous les projets Java doivent comporter un fichier
Main.javaavec la classeMain. C'est ici que vous testez vos classes en créant des objets à partir d'elles.Pour exécuter du code Java, nous utiliserons IntelliJ IDEAun IDE Java très répandu. Après avoir installé IntelliJ :
Main.javaet collez le contenu suivant :Lorsque nous parlons de classes, nous écrivons du code dans d'autres fichiers que le fichier
Main.java. Mais s'il s'agit de créer et de tester desobjets , nous passons àMain.java.Pour exécuter le programme, vous pouvez cliquer sur le bouton vert de lecture situé à côté de la méthode principale :
La sortie sera affichée dans la fenêtre de l'outil Exécuter en bas.
Si vous êtes totalement novice en matière de Java, veuillez consulter notre cours d'introduction à Java. Cours d'introduction à Javaqui couvre les principes fondamentaux des types de données et du flux de contrôle Java avant de poursuivre.
Sinon, plongeons dans le vif du sujet.
Classes et objets Java
Qu'est-ce qu'une classe, exactement ?
Les classes sont des constructions de programmation en Java qui permettent de représenter des concepts du monde réel. Par exemple, considérez cette classe MenuItem (créez un fichier pour écrire cette classe dans votre IDE) :
La classe nous donne un plan ou un modèle pour représenter les différents éléments du menu d'un restaurant. En modifiant les deux attributs de la classe,
name, etprice, nous pouvons créer d'innombrables objets de menu comme un hamburger ou une salade.Ainsi, pour créer une classe en Java, vous commencez par une ligne décrivant le niveau d'accès de la classe (
private,public, ouprotected) suivie du nom de la classe. Immédiatement après la mise entre parenthèses, vous décrivez les attributs de votre classe.Mais comment créer des objets appartenant à cette classe ? Java permet cela grâce à des méthodes de construction :
Un constructeur est une méthode spéciale qui est appelée lorsque nous créons un nouvel objet à partir d'une classe. Il initialise les attributs de l'objet avec les valeurs que nous avons fournies. Dans l'exemple ci-dessus, le constructeur prend un nom et un prix en paramètre et les affecte aux champs de l'objet en utilisant le mot-clé "this" pour faire référence à une future instance de l'objet.
La syntaxe du constructeur est différente de celle des autres méthodes de classe car elle ne nécessite pas de spécifier un type de retour. En outre, le constructeur doit porter le même nom que la classe et doit avoir le même nombre d'attributs que celui que vous avez déclaré après la définition de la classe. Ci-dessus, le constructeur crée deux attributs parce que nous en avons déclaré deux après la définition de la classe :
nameetprice.Après avoir écrit votre classe et son constructeur, vous pouvez créer des instances (objets) dans votre méthode principale :
Sortie :
Ci-dessus, nous créons deux objets
MenuItemdans les variablesburgeretsalad. Comme l'exige Java, le type de la variable doit être déclaré, à savoirMenuItem. Ensuite, pour créer une instance de notre classe, nous écrivons le mot-clé new suivi de l'invocation de la méthode du constructeur.Outre le constructeur, vous pouvez créer des méthodes ordinaires qui confèrent un comportement à votre classe. Par exemple, ci-dessous, nous ajoutons une méthode pour calculer le prix total après taxes :
Nous pouvons maintenant calculer le prix total, taxes comprises :
Sortie :
Encapsulation
L'objectif des classes est de fournir un schéma directeur pour la création d'objets. Ces objets seront ensuite utilisés par d'autres scripts ou programmes. Par exemple, nos objets
MenuItempeuvent être utilisés par une interface utilisateur qui affiche leur nom, leur prix et leur image à l'écran.C'est pourquoi nous devons concevoir nos classes de manière à ce que leurs instances ne puissent être utilisées que de la manière que nous avons prévue. Pour l'instant, notre classe
MenuItemest très basique et sujette aux erreurs. Une personne peut créer des objets aux attributs ridicules, comme une tarte aux pommes à prix réduit ou un sandwich à un million de dollars :Ainsi, la première chose à faire après avoir écrit une classe est de protéger ses attributs en limitant la façon dont ils sont créés et accédés. Pour commencer, nous voulons n'autoriser que des valeurs positives pour leprix et fixer une valeur maximale pour éviter d'afficher accidentellement des articles ridiculement chers.
Java nous permet d'accomplir cette tâche en utilisant des méthodes de type "setter" :
Examinons les nouveautés du bloc de code ci-dessus :
1. Nous avons rendu les attributs privés en ajoutant le mot-clé
private. Cela signifie qu'ils ne sont accessibles qu'à l'intérieur de la classe MenuItem. L'encapsulation commence par cette étape cruciale.2. Nous avons ajouté une nouvelle constante:
MAX_PRICE:3. Nous avons ajouté une méthode
setPrice()qui :4. Nous avons modifié le constructeur pour utiliser
setPrice()au lieu d'assigner directement le prix. Cela permet de s'assurer que la validation des prix a lieu lors de la création de l'objet.Nous venons de mettre en œuvre l'un des principaux piliers d'une bonne conception orientée objet - l'encapsulation. Ce paradigme permet de masquer les données et de contrôler l'accès aux attributs des objets, en garantissant que les détails de l'implémentation interne sont protégés des interférences externes et ne peuvent être modifiés qu'au moyen d'interfaces bien définies.
Faisons valoir notre point de vue en appliquant l'encapsulation à l'attributname. Imaginez un café qui ne sert que des lattes, des cappuccinos, des espressos, des americanos et des mokas.
Ainsi, les noms de nos éléments de menu ne peuvent être que l'un des éléments de cette liste. Voici comment nous pouvons appliquer cette règle dans le code :
Le code ci-dessus met en œuvre la validation des noms pour les éléments du menu d'un café. Voyons cela en détail :
1. Tout d'abord, il définit un tableau final statique privé
VALID_NAMESqui contient les seuls noms de boissons autorisés : latte, cappuccino, espresso, americano et mocha. Ce tableau étant :2. Il déclare un champ privé String name pour stocker le nom de la boisson.
3. La méthode
setName()met en œuvre la logique de validation :VALID_NAMESVoici la classe complète jusqu'à présent :
Après avoir protégé la façon dont les attributs sont créés, nous voulons également protéger la façon dont on y accède. Pour ce faire, vous pouvez utiliser des méthodes de type "getter" :
Les méthodes Getter fournissent un accès contrôlé aux attributs privés d'une classe. Ils résolvent le problème de l'accès direct aux attributs, qui peut entraîner des modifications non souhaitées et rompre l'encapsulation.
Par exemple, sans getters, nous pourrions accéder directement aux attributs :
Avec les getters, nous assurons un accès approprié :
Cette encapsulation :
Héritage
Notre classe commence à avoir fière allure, mais elle présente de nombreux problèmes. Par exemple, pour un grand restaurant servant de nombreux types de plats et de boissons, la classe n'est pas assez flexible.
Si nous voulons ajouter différents types de produits alimentaires, nous nous heurterons à plusieurs difficultés. Certains plats peuvent être préparés pour être emportés, tandis que d'autres doivent être consommés immédiatement. Les prix et les remises peuvent varier d'un produit à l'autre. Les plats peuvent nécessiter un suivi de la température ou un stockage spécial. Les boissons peuvent être chaudes ou froides avec des ingrédients personnalisables. Les articles peuvent nécessiter des informations sur les allergènes et des options de portions. Le système actuel ne permet pas de répondre à ces différentes exigences.
L'héritage offre une solution élégante à tous ces problèmes. Il nous permet de créer des versions spécialisées d'éléments de menu en définissant une classe de base
MenuItemavec des attributs communs, puis en créant des classes enfantines qui héritent de ces éléments de base tout en ajoutant des caractéristiques uniques.Par exemple, nous pourrions avoir une classe
Drinkpour les boissons avec des options de température, une classeFoodpour les articles nécessitant une consommation immédiate et une classeDessertpour les articles ayant des besoins de stockage particuliers - toutes ces classes héritant de la fonctionnalité principale des articles de menu.Extension des classes
Mettons en œuvre ces idées en commençant par
Drink:Pour définir une classe enfant qui hérite d'une classe parent, nous utilisons le mot-clé
extendsaprès le nom de la classe enfant, suivi de celui de la classe parent. Après la définition de la classe, nous définissons les nouveaux attributs de cet enfant et implémentons son constructeur.Mais remarquez que nous devons répéter l'initialisation de
nameetpriceavecisCold. Ce n'est pas idéal car la classe mère peut avoir des centaines d'attributs. En outre, le code ci-dessus provoquera une erreur lorsque vous le compilerez, car ce n'est pas la bonne façon d'initialiser les attributs de la classe parente. La bonne méthode consiste à utiliser le mot-clésuper:Le mot-clé
superest utilisé pour appeler le constructeur de la classe mère. Dans ce cas,super(name, price)appelle le constructeur deMenuItempour initialiser ces attributs, évitant ainsi la duplication du code. Il suffit d'initialiser le nouvel attributisColdspécifique à la classeDrink.Ce mot-clé est très flexible car vous pouvez l'utiliser pour faire référence à la classe mère dans n'importe quelle partie de la classe enfant. Par exemple, pour appeler une méthode parent, vous utilisez
super.methodName(), tandis quesuper.attributeNameest destiné aux attributs.Remise en cause de la méthode
Supposons maintenant que nous voulions ajouter une nouvelle méthode à nos classes pour calculer le prix total après taxes. Étant donné que les taux d'imposition peuvent varier d'un article de menu à l'autre (par exemple, aliments préparés ou boissons emballées), nous pouvons utiliser la méthode de superposition pour mettre en œuvre des calculs d'impôts spécifiques dans chaque classe enfant tout en conservant un nom de méthode commun dans la classe mère.
Voici à quoi cela ressemble :
Dans cet exemple, la superposition de méthodes permet à chaque sous-classe de fournir sa propre implémentation de la méthode
calculateTotalPrice():La classe de base
MenuItemdéfinit un calcul de taxe par défaut de 10 %.Quand
FoodetDrinkétendentMenuItemils surchargent cette méthode pour mettre en œuvre leurs propres taux d'imposition :L'annotation
@Overrideest utilisée pour indiquer explicitement que ces méthodes remplacent la méthode de la classe mère. Cela permet de détecter les erreurs si la signature de la méthode ne correspond pas à celle de la classe mère.Chaque sous-classe peut toujours accéder au prix de la classe parente en utilisant
super.getPrice()démontrant ainsi que les méthodes surchargées peuvent utiliser les fonctionnalités de la classe parente tout en ajoutant leur propre comportement.En bref, la superposition de méthodes fait partie intégrante de l'héritage et permet aux sous-classes de fournir leur propre implémentation des méthodes définies dans la classe mère, ce qui permet d'obtenir un comportement plus spécifique tout en conservant la même signature de méthode.
Classes abstraites
Notre hiérarchie de classes
MenuItemfonctionne, mais il y a un problème : n'importe qui devrait pouvoir créer un simple objetMenuItem? Après tout, dans notre restaurant, chaque élément du menu est soit un plat, soit une boisson - il n'existe pas d'élément de menu générique.Nous pouvons éviter cela en faisant de
MenuItemune classe abstraite. Une classe abstraite ne fournit qu'un modèle de base - elle ne peut être utilisée que comme classe parente pour l'héritage, et ne peut pas être instanciée directement.Pour rendre
MenuItemabstrait, nous ajoutons le mot-cléabstractaprès son modificateur d'accès :Les classes abstraites peuvent également avoir des méthodes abstraites comme
calculateTotalPrice()ci-dessus. Ces méthodes abstraites servent de contrats qui obligent les sous-classes à fournir leurs implémentations. En d'autres termes, toute méthode abstraite d'une classe abstraite doit être implémentée par les classes enfantines.Réécrivons donc
FoodetDrinken tenant compte de ces changements :Grâce à la mise en œuvre de ce système de menus, nous avons vu comment l'abstraction et l'héritage fonctionnent ensemble pour créer un code flexible et facile à maintenir qui peut facilement s'adapter aux différentes exigences de l'entreprise.
Conclusion
Aujourd'hui, nous avons eu un aperçu de ce dont Java est capable en tant que langage de programmation orienté objet. Nous avons couvert les bases des classes, des objets et quelques piliers de la POO : encapsulation, héritage et abstraction à travers un système de menu de restaurant.
Pour que ce système soit prêt pour la production, il vous reste encore beaucoup de choses à apprendre, comme les interfaces (qui font partie de l'abstraction), le polymorphisme et les modèles de conception OOP. Pour en savoir plus sur ces concepts, reportez-vous à notre Introduction à la POO en Java.
Si vous souhaitez tester vos connaissances en Java, essayez de répondre à certaines des questions de notre article sur les questions d'entretien en Java. notre article Questions d'entretien sur Java.