Classes abstraites en Java

1. Vue d'ensemble

Il existe de nombreux cas lors de la mise en œuvre d'un contrat où nous souhaitons reporter certaines parties de la mise en œuvre pour être complétées plus tard. Nous pouvons facilement accomplir cela en Java grâce à des classes abstraites.

Dans ce didacticiel, nous allons apprendre les bases des classes abstraites en Java et dans quels cas elles peuvent être utiles .

2. Concepts clés pour les classes abstraites

Avant de vous demander quand utiliser une classe abstraite, examinons leurs caractéristiques les plus pertinentes :

  • Nous définissons une classe abstraite avec le modificateur abstrait précédant le mot - clé class
  • Une classe abstraite peut être sous-classée, mais elle ne peut pas être instanciée
  • Si une classe définit une ou plusieurs méthodes abstraites , alors la classe elle-même doit être déclarée abstraite
  • Une classe abstraite peut déclarer des méthodes abstraites et concrètes
  • Une sous-classe dérivée d'une classe abstraite doit soit implémenter toutes les méthodes abstraites de la classe de base, soit être abstraite elle-même

Pour mieux comprendre ces concepts, nous allons créer un exemple simple.

Faisons en sorte que notre classe abstraite de base définisse l'API abstraite d'un jeu de société:

public abstract class BoardGame { //... field declarations, constructors public abstract void play(); //... concrete methods }

Ensuite, nous pouvons créer une sous-classe qui implémente la méthode de lecture :

public class Checkers extends BoardGame { public void play() { //... implementation } }

3. Quand utiliser les classes abstraites

Maintenant, analysons quelques scénarios typiques où nous devrions préférer les classes abstraites aux interfaces et aux classes concrètes:

  • Nous voulons encapsuler certaines fonctionnalités communes en un seul endroit (réutilisation du code) que plusieurs sous-classes associées partageront
  • Nous devons définir partiellement une API que nos sous-classes peuvent facilement étendre et affiner
  • Les sous-classes doivent hériter d'une ou plusieurs méthodes ou champs courants avec des modificateurs d'accès protégés

Gardons à l'esprit que tous ces scénarios sont de bons exemples d'adhésion complète, basée sur l'héritage, au principe Ouvert / Fermé.

De plus, puisque l'utilisation de classes abstraites traite implicitement des types de base et des sous-types, nous tirons également parti du polymorphisme.

Notez que la réutilisation du code est une raison très convaincante d'utiliser des classes abstraites, tant que la relation «est-un» dans la hiérarchie de classes est préservée.

Et Java 8 ajoute une autre ride avec les méthodes par défaut, qui peuvent parfois remplacer le besoin de créer une classe abstraite.

4. Exemple de hiérarchie de lecteurs de fichiers

Pour comprendre plus clairement les fonctionnalités que les classes abstraites apportent à la table, regardons un autre exemple.

4.1. Définition d'une classe abstraite de base

Donc, si nous voulions avoir plusieurs types de lecteurs de fichiers, nous pourrions créer une classe abstraite qui encapsule ce qui est commun à la lecture de fichiers:

public abstract class BaseFileReader { protected Path filePath; protected BaseFileReader(Path filePath) { this.filePath = filePath; } public Path getFilePath() { return filePath; } public List readFile() throws IOException { return Files.lines(filePath) .map(this::mapFileLine).collect(Collectors.toList()); } protected abstract String mapFileLine(String line); }

Notez que nous avons protégé filePath afin que les sous-classes puissent y accéder si nécessaire. Plus important encore, nous avons laissé quelque chose en suspens: comment analyser réellement une ligne de texte à partir du contenu du fichier.

Notre plan est simple: bien que nos classes concrètes n'aient pas chacune une manière spéciale de stocker le chemin du fichier ou de parcourir le fichier, elles auront chacune une manière spéciale de transformer chaque ligne.

À première vue, BaseFileReader peut sembler inutile. Cependant, c'est la base d'une conception propre et facilement extensible. À partir de là, nous pouvons facilement implémenter différentes versions d'un lecteur de fichiers qui peuvent se concentrer sur leur logique métier unique .

4.2. Définition des sous-classes

Une implémentation naturelle est probablement celle qui convertit le contenu d'un fichier en minuscules:

public class LowercaseFileReader extends BaseFileReader { public LowercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toLowerCase(); } }

Ou un autre peut être celui qui convertit le contenu d'un fichier en majuscules:

public class UppercaseFileReader extends BaseFileReader { public UppercaseFileReader(Path filePath) { super(filePath); } @Override public String mapFileLine(String line) { return line.toUpperCase(); } }

Comme nous pouvons le voir dans cet exemple simple, chaque sous-classe peut se concentrer sur son comportement unique sans avoir besoin de spécifier d'autres aspects de la lecture de fichiers.

4.3. Utilisation d'une sous-classe

Enfin, l'utilisation d'une classe qui hérite d'une classe abstraite n'est pas différente de toute autre classe concrète:

@Test public void givenLowercaseFileReaderInstance_whenCalledreadFile_thenCorrect() throws Exception { URL location = getClass().getClassLoader().getResource("files/test.txt") Path path = Paths.get(location.toURI()); BaseFileReader lowercaseFileReader = new LowercaseFileReader(path); assertThat(lowercaseFileReader.readFile()).isInstanceOf(List.class); }

Par souci de simplicité, le fichier cible se trouve sous le dossier src / main / resources / files . Par conséquent, nous avons utilisé un chargeur de classe d'application pour obtenir le chemin du fichier d'exemple. N'hésitez pas à consulter notre tutoriel sur les chargeurs de classes en Java.

5. Conclusion

Dans cet article rapide, nous avons appris les bases des classes abstraites en Java, et quand les utiliser pour réaliser l'abstraction et encapsuler une implémentation commune en un seul endroit .

Comme d'habitude, tous les exemples de code présentés dans ce didacticiel sont disponibles à l'adresse over sur GitHub.