Théorie 1.2.3: visibilités et attributs #
-
Une méthode peut être visible (
public
) ou cachée (private
), selon le contexte -
Si la méthode est cachée pour moi, je ne peux pas l’appeler
-
Une méthode cachée n’apparaîtra pas dans les auto-complétions d’Eclipse
Pourquoi différentes visibilités? #
-
Prenons l’exemple de l’outil de validation.
-
Le prof ne veut pas que l’étudiant voit tout le code de l’outil de validation
-
par exemple, le prof veut:
-
et non:
-
la méthode
validateTestCases
ne doit pas être appelée par l’étudiant -
alors vaut mieux la cacher en la déclarant
private
-
API: Application Programming Interface #
-
La visibilité est fortement liée à la notion d’API
-
L’API document les méthodes d’une librairie que l’application peut appeler
-
P.ex. l’API Java documente les méthodes utiles à un programmeur Java
-
Il n’a pas de méthodes
private
dans cette documentation.- le programmeur n’en a pas besoin
- les détails d’implantation sont inutiles 99% du temps
Les règles clés #
- Voici les règles de base pour la visibilité
Visible de l'extérieur de la classe | Visible des sous-classes | Héritable/redéfinissable | |
---|---|---|---|
private |
NON | NON | NON |
protected |
NON | OUI | OUI |
public |
OUI | OUI | OUI |
- Il y a d’autres règles, mais les règles ci-haut couvrent 90% des cas.
Quelle visibilité choisir? #
- Il faut se poser les questions:
- Est-ce que le reste du programme a besoin d’appeler cette méthode?
- si oui, alors choisir
public
- si oui, alors choisir
- Est-ce qu’une sous-classe aura besoin de redéfinir cette méthode?
- si oui, alors choisir
protected
- si oui, alors choisir
- Sinon, choisir
private
- Est-ce que le reste du programme a besoin d’appeler cette méthode?
Les attributs et la visibilité #
-
Il y a deux types d’attributs:
- attribut défini: l’attribut est défini dans la classe
- attribut hérité: l’attribut est défini dans la classe parent (
public
ouprotected
)
-
Il n’y a pas d’attribut redéfini.
- pour accéder à l’attribut d’une sous-classe, il faut utiliser une méthode
Les attributs représentent l’état privé d’un objet #
-
En général, tous les attributs d’un objet sont cachés (
private
) -
Les attributs représentent l’état de l’objet, p.ex:
- le kilométrage parcouru par le véhicule
- le nombre d’oiseaux attrapés par le chat
-
L’état est ce qui peut changer en cours d’exécution
- (alors que le code des méthodes reste toujours le même)
-
L’état est privé parce que l’objet doit toujours être appeler de la même façon
- l’état est un détail d’implantation qui ne concerne pas le reste du programme
Exemple: accepterChargement
et consommationLitresParKilometres
#
- Considérer la classe
Vehicule
:
public class Vehicule {
private double totalKilometres = 0;
public void rouler(double kilometres) {
totalKilometres += kilometres;
}
private double litresEssenceConsomes() {
return totalKilometres * consommationLitresParKilometre();
}
}
-
Le calcul de consommation d’essence est simple
-
Dans la classe
Camion
, on ajout une notion de chargement:
public class Camion extends Vehicule {
private double consommationDeBase = 14;
private double chargementEnKilos = 0;
public void accepterChargement(double chargementEnKilos) {
this.chargementEnKilos = chargementEnKilos;
}
@Override
protected double consommationLitresParKilometre() {
return calculerConsommationSelonChargement();
}
private double calculerConsommationSelonChargement() {
return (1 + chargementEnKilos / 1E6) * consommationDeBase;
}
}
-
La notion de chargement est cachée grace à la visibilité
private
-
On ne veut vraiment pas que le
Vehicule
gère la notion de chargement:
public class Vehicule {
private double totalKilometres = 0;
public void rouler(double kilometres) {
totalKilometres += kilometres;
}
private double litresEssenceConsomes() {
double litresConsomes = 0;
if(this instanceof Camion){ // instanceof
double chargementEnKilos = ((Camion) this).getChargementEnKilos(); // getter
double consommationDeBase = ((Camion) this).getConsommationDeBase(); // getter
double consommationSelonChargement = (1 + chargementEnKilos / 1E6) * consommationDeBase;
litresConsomes = totalKilometres * consommationSelonChargement;
}else{
litresConsomes = totalKilometres * consommationLitresParKilometre();
}
return litresConsomes;
}
}
-
Le code serait beaucoup plus compliqué et difficile à lire
-
En plus, on veut que
Camion
puisse changer son code privé
public class Camion extends Vehicule {
private double consommationDeBase = 14;
private double chargementEnTonnes = 0; // TONNES
public void accepterChargement(double chargementEnKilos) {
this.chargementEnTonnes = chargementEnKilos / 1E3; // TONNES
}
@Override
protected double consommationLitresParKilometre() {
return calculerConsommationSelonChargement();
}
private double calculerConsommationSelonChargement() {
return (1 + chargementEnTonnes / 1E3) * consommationDeBase; // TONNES
}
}
Camion
représente maintenant le poids du chargement en tonnes- toutes les modifications sont privées à la classe
Camion
- il n’y a rien à changer ailleurs dans le code
Exemple: formater
#
- Considérer la méthode pour
formater
public class Vehicule {
public String formater() {
StringBuilder builder = new StringBuilder();
if(siNomFeminin()) {
builder.append("Ma ");
}else {
builder.append("Mon ");
}
builder.append(nomVehicule());
builder.append(" a roulé ");
builder.append(totalKilometres);
builder.append(" kilomètres et consomé ");
builder.append(litresEssenceConsomes());
builder.append(" litres d'essence.");
return builder.toString();
}
}
-
La méthode est simple parce qu’on délègue le plus possible
-
Si on ne délègue pas, ça donne plutôt:
public class Vehicule {
public String formater() {
StringBuilder builder = new StringBuilder();
if(this instanceof Auto){ // instanceof
builder.append("Mon");
}else if(this instanceof Camion){ // instanceof
builder.append("Mon");
}else if(this instanceof Moto){ // instanceof
builder.append("Ma");
}else if(this instanceof Mobilette){ // instanceof
builder.append("Ma");
}
if(this instanceof Auto){ // instanceof
builder.append(" auto");
}else if(this instanceof Camion){ // instanceof
builder.append(" camion");
}else if(this instanceof Moto){ // instanceof
builder.append(" moto");
}else if(this instanceof Mobilette){ // instanceof
builder.append(" mobilette");
}
builder.append(" a roulé ");
builder.append(totalDesKilometres());
builder.append(" kilomètres et consomé ");
builder.append(litresEssenceConsomes());
builder.append(" litres d'essence.");
return builder.toString();
}
}
-
Imaginer qu’on ajoute la sous-classe
Fourgonnette
-
Pour la première version, il n’y a rien à modifier!
-
Pour la deuxième version, il faut ajouter du code:
public class Vehicule {
public String formater() {
StringBuilder builder = new StringBuilder();
if(this instanceof Auto){ // instanceof
builder.append("Mon");
}else if(this instanceof Camion){ // instanceof
builder.append("Mon");
}else if(this instanceof Moto){ // instanceof
builder.append("Ma");
}else if(this instanceof Mobilette){ // instanceof
builder.append("Ma");
// FOURGONNETTE
}else if(this instanceof Fourgonnette){ // instanceof
builder.append("Ma");
}
if(this instanceof Auto){ // instanceof
builder.append(" auto");
}else if(this instanceof Camion){ // instanceof
builder.append(" camion");
}else if(this instanceof Moto){ // instanceof
builder.append(" moto");
}else if(this instanceof Mobilette){ // instanceof
builder.append(" mobilette");
// FOURGONNETTE
}else if(this instanceof Fourgonnette){ // instanceof
builder.append(" fourgonnette");
}
builder.append(" a roulé ");
builder.append(totalDesKilometres());
builder.append(" kilomètres et consomé ");
builder.append(litresEssenceConsomes());
builder.append(" litres d'essence.");
return builder.toString();
}
}
Pour écrire du code lisible: apprendre à déléguer #
-
j’imagine que ma classe est une librairie pour le reste du programme
- qu’est-ce que ma classe devrait cacher? Rendre public?
-
j’essaie de déléguer un comportement dès que possible:
- à une autre méthode
- à une autre classe
- à une sous-classe
-
en particulier, je me méfie des cas où:
- ma méthode a plus de 20 lignes
- je peux sûrement déléguer une partie à une autre méthode
- j’utilise beaucoup de
instanceof
et de transtypage (casting)- je peux sûrement déléguer aux sous-classe
- j’utilise beaucoup d’accesseurs (getter)
- je peux sûrement déléguer à l’objet que j’appelle
- ma méthode a plus de 20 lignes
-
NOTES:
- les règles ci-haut couvrent 90% des cas
- comme d’habitude, il y a des exceptions
- il y a des cas où une méthode de 20+ lignes est plus lisible
- il y a des cas où utiliser un accesseur est plus lisible
- il y a des cas où utiliser
instanceof
est plus lisible