Classes abstraites
#
Les classes abstraites sont un autre outil d’abstraction. Elles servent de base abstraite pour l’héritage de classes concrètes. Voici quelques considérations sur les classes abstraites:
- tout comme les interfaces, elles ne peuvent être instanciées;
- à la différence des interfaces, elles peuvent avoir des champs (et donc décrire des transitions d’états) et peuvent fournir des constructeurs qui pourront être utilisés par des classes héritées;
- l’héritage multiple n’existe pas pour les classes (concrètes ou abstraites). Alors qu’une classe peut hériter de plusieurs interfaces (mot-clé
implements
), elle ne peut hériter que d’une seule classe (mot-clé extends
);
- une classe qui hérite d’une classe abstraite doit redéfénir toutes les méthodes abstraites de celle-ci pour devenir concrète, sinon elle reste abstraite;
- une classe abstraite n’a pas obligatoirement de méthodes abstraites.
Avant Java 8, une interface ne pouvait avoir de méthode concrète. La classe abstraite était un élément de choix pour éviter la duplication de code. Avec les versions récentes de Java, il est toujours préférable de privilégier les interfaces.
Regardons la syntaxe en proposant une classe abstraite Clickable
qui propse de garder une valeur entière en mémoire et d’appliquer une action lorsque la méthode click
est appelée. Celle-ci devra être redéfinie par les classes concrètes.
Notre version originale de notre compteur est donnée ci-dessous:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class Counter {
private int counter;
private Counter(int i) {
this.safeSetCounter(i);
}
public void click() {
this.safeSetCounter(this.counter + 1);
}
private void safeSetCounter(int i) {
this.counter = i % 10000;
}
public void reset() {
this.counter = 0;
}
public int value() {
return this.counter;
}
public static Counter zero() {
return new Counter(0);
}
public static Counter withValue(int value) {
if (value < 0 || value > 9999) {
/* Nous préférons retourner une exception si l'utilisateur fait une bêtise */
throw new IllegalArgumentException("Value must be between 0 and 9999");
}
return new Counter(value);
}
private String toFourDigits(int i) {
final String tooMuchDigits = ("0000" + this.counter);
return tooMuchDigits.substring( tooMuchDigits.length()-4, tooMuchDigits.length() );
}
public String toString() {
return "Counter(" + toFourDigits(this.counter) + ")";
}
}
|
Version avec une classe abstraite
#
Voici maintenant notre classe abstraite Clickable
:
1
2
3
4
5
6
7
|
// Clickable.java
abstract class Clickable {
protected int i;
protected Clickable(int i) { this.i = i; }
public int value() { return this.i; }
abstract void click();
}
|
Remaques:
- le mot clé
protected
indique qu’un membre n’est visible que pour les sous-classes. Les membres privés ne sont pas visibles auprès des sous-classes.
Et notre classe concrète Counter
peut maintenant hériter de Clickable
pour réduire une partie de la logique. Elle doit redéfinir obligatoirement la méthode click
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
/* +----------- mot clé pour l'héritage de classes
v */
public class Counter extends Clickable {
private Counter(int i) {
super(i); // Appel obligatoire à un constructeur de la classe abstraite
this.safeSetCounter(i); // vérification ensuite
}
public void click() {
this.safeSetCounter(this.i + 1);
}
private void safeSetCounter(int i) {
this.i = i % 10000;
}
public void reset() {
this.i = 0;
}
public static Counter zero() {
return new Counter(0);
}
public static Counter withValue(int value) {
if (value < 0 || value > 9999) {
/* Nous préférons retourner une exception si l'utilisateur fait une bêtise */
throw new IllegalArgumentException("Value must be between 0 and 9999");
}
return new Counter(value);
}
private String toFourDigits(int i) {
final String tooMuchDigits = ("0000" + this.i);
return tooMuchDigits.substring( tooMuchDigits.length()-4, tooMuchDigits.length() );
}
public String toString() {
return "Counter(" + toFourDigits(this.i) + ")";
}
}
|
Nous pouvons maintenant créer un autre composant clickable :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class DummyCounter extends Clickable {
public DummyCounter(int i) {
super(i);
}
public void click() {
this.i += 1;
}
public String toString() {
return "DummyCounter(" + this.i + ")";
}
}
|
Voici un exemple utilisant le polymorphisme :
1
2
3
4
5
6
7
8
|
Clickable c = Counter.zero();
c.click();
c.click();
System.out.println(c); // ==> Counter(0002)
c = new DummyCounter(40);
c.click();
c.click();
System.out.println(c); // ==> DummyCounter(42)
|