1. Avant de commencer
Conditions préalables
- Vous maîtrisez l'utilisation de Kotlin Playground pour modifier des programmes Kotlin.
- Les concepts de base de la programmation en Kotlin sont présentés dans le module 1 de ce cours. Il s'agit, plus précisément, du programme
main()
, des fonctions avec arguments qui renvoient des valeurs, des variables, des types de données et des opérations, ainsi que des instructionsif/else
. - Vous êtes capable de définir une classe en langage Kotlin, de créer une instance d'objet à partir de celle-ci, et d'accéder à ses propriétés et méthodes.
Points abordés
- Créer un programme Kotlin qui utilise l'héritage pour implémenter une hiérarchie de classes.
- Étendre une classe, remplacer ses fonctionnalités existantes et en ajouter de nouvelles.
- Choisir le modificateur de visibilité approprié pour les variables.
Objectifs de l'atelier
- Programme Kotlin avec différents types de logements implémentés sous la forme d'une hiérarchie de classes.
Ce dont vous avez besoin
- Un ordinateur avec une connexion Internet pour accéder à Kotlin Playground.
2. Qu'est-ce qu'une hiérarchie de classes ?
Il est dans la nature humaine de classer des éléments ayant des propriétés et un comportement similaires en groupes, voire de former une sorte de hiérarchie entre eux. Vous pouvez définir, par exemple, une vaste catégorie regroupant les légumes, dans laquelle une sous-catégorie rassemble un type plus spécifique, comme les légumineuses. Parmi les légumineuses, il peut y avoir des types encore plus spécifiques, comme les pois, les haricots, les lentilles, les pois chiches et le soja.
Cela peut être représenté sous la forme d'une hiérarchie, car les légumineuses contiennent ou héritent de toutes les propriétés des légumes (par exemple, ce sont des plantes et elles sont comestibles). De même, les pois, haricots et lentilles ont tous les propriétés des légumineuses, ainsi que des propriétés uniques.
Voyons maintenant comment représenter cette relation dans un langage de programmation. Si vous définissez Vegetable
comme classe en Kotlin, vous pouvez créer Legume
en tant qu'enfant ou que sous-classe de la classe Vegetable
. Cela signifie que la classe Legume
hérite de toutes les propriétés et méthodes de la classe Vegetable
(autrement dit, elles y sont également disponibles).
Vous pouvez représenter cela dans un diagramme de hiérarchie de classes, comme illustré ci-dessous. Vous pouvez désigner Vegetable
comme parent ou super-classe de la classe Legume
.
Vous pouvez continuer à développer la hiérarchie de classes en créant des sous-classes de Legume
, telles que Lentil
et Chickpea
. Legume
devient alors un enfant ou une sous-classe de Vegetable
, ainsi qu'un parent ou une super-classe de Lentil
et Chickpea
. Vegetable
est la classe racine ou de niveau supérieur (ou encore de base) de cette hiérarchie.
Héritage dans les classes Android
Bien que vous puissiez écrire du code Kotlin sans utiliser de classes, comme vous l'avez fait dans les ateliers précédents, de nombreuses parties d'Android vous sont proposées sous la forme de classes, avec des activités, des vues et des groupes de vues. Il est donc essentiel de comprendre le concept des hiérarchies de classes pour développer des applications Android. Cela vous permet, en outre, de bénéficier des fonctionnalités fournies par le framework Android.
Par exemple, il existe dans Android une classe View
qui représente une zone rectangulaire à l'écran, et qui est chargée du dessin et de la gestion des événements. La classe TextView
est une sous-classe de View
, ce qui signifie que TextView
hérite de toutes les propriétés et fonctionnalités de View
, et ajoute une logique spécifique pour présenter du texte à l'utilisateur.
On peut même dire que les classes EditText
et Button
sont des enfants de la classe TextView
. Elles héritent de toutes les propriétés et méthodes des classes TextView
et View
, et ajoutent en outre leur propre logique. Par exemple, EditText
ajoute sa propre fonctionnalité de modification de texte à l'écran.
Au lieu de copier et de coller toute la logique des classes View
et TextView
dans la classe EditText
, EditText
peut simplement sous-classer TextView
qui, à son tour, sous-classe la classe View
. Ensuite, le code de la classe EditText
peut se concentrer spécifiquement sur ce qui différencie ce composant d'interface utilisateur des autres vues.
Le diagramme de hiérarchie de classes s'affiche en haut d'une page de documentation d'une classe Android sur le site Web developer.android.com. Si kotlin.Any
est indiqué en haut de la hiérarchie, cela est dû au fait que toutes les classes en langage Kotlin possèdent une super-classe commune appelée Any. En savoir plus
Comme vous pouvez le constater, apprendre à utiliser l'héritage entre les classes peut faciliter l'écriture, la réutilisation, la lecture et le test du code.
3. Créer une classe de base
Hiérarchie de classes de logements
Dans cet atelier de programmation, vous allez créer un programme Kotlin qui illustre le fonctionnement des hiérarchies de classes en utilisant des logements avec une surface utile, des étages et des résidents.
Voici un diagramme de la hiérarchie de classes que vous allez créer. À la racine, une classe Dwelling
spécifie les propriétés et fonctionnalités qui sont vraies pour tous les logements, comme sur un plan. On trouve ensuite des classes pour une cabane carrée (SquareCabin
), une hutte ronde (RoundHut
) et une tour ronde (RoundTower
), qui est un élément RoundHut
avec plusieurs étages.
Les classes que vous allez implémenter sont les suivantes :
Dwelling
: classe de base représentant un abri non spécifique contenant des informations communes à tous les logements.SquareCabin
: cabane carrée en bois, avec une aire de plancher carrée.RoundHut
: cabane ronde en paille, avec une aire de plancher circulaire ; classe parente deRoundTower
.RoundTower
: tour ronde en pierre avec une aire de plancher circulaire et plusieurs étages.
Créer une classe Dwelling abstraite
N'importe quelle classe peut être la classe de base d'une hiérarchie de classes ou le parent d'autres classes.
Une classe "abstraite" est une classe qui ne peut pas être instanciée, car elle n'est pas complètement implémentée. On peut la comparer à un croquis. Un croquis contient les idées et les lignes directrices d'un projet, mais ne fournit généralement pas assez d'informations pour la construction. Vous allez utiliser un croquis (classe abstraite) pour créer un plan (classe) à partir duquel vous construirez l'instance d'objet réelle.
L'avantage d'une super-classe est de contenir des propriétés et fonctions communes à toutes ses sous-classes. Si les valeurs des propriétés et les implémentations des fonctions ne sont pas connues, rendez la classe abstraite. Par exemple, Vegetables
possède de nombreuses propriétés communes à tous les légumes, mais vous ne pouvez pas créer une instance d'un légume non spécifique, car vous ne connaissez ni sa forme, ni sa couleur. Ainsi, Vegetable
est une classe abstraite qui laisse le soin aux sous-classes de déterminer des détails spécifiques sur chaque légume.
La déclaration d'une classe abstraite commence par le mot clé abstract
.
Dwelling
sera une classe abstraite comme Vegetable
. Elle contient des propriétés et des fonctions communes à de nombreux types de logements, mais les valeurs exactes des propriétés et les détails d'implémentation des fonctions ne sont pas connus.
- Accédez à Kotlin Playground à l'adresse https://developer.android.com/training/kotlinplayground.
- Dans l'éditeur, supprimez
println("Hello, world!")
dans la fonctionmain()
. - Ajoutez ensuite ce code sous la fonction
main()
pour créer une classeabstract
appeléeDwelling
.
abstract class Dwelling(){
}
Ajouter une propriété pour les matériaux de construction
Dans la classe Dwelling
, vous définissez des éléments qui sont vrais pour tous les logements, même s'ils peuvent varier pour certains d'entre eux. Dans tous les cas, on a recours à des matériaux de construction.
- Dans
Dwelling
, créez une variablebuildingMaterial
de typeString
pour représenter le matériau de construction. Étant donné que les matériaux de construction ne changent pas, utilisezval
pour en faire une variable non modifiable.
val buildingMaterial: String
- Exécutez votre programme. Vous obtenez alors ce message d'erreur.
Property must be initialized or be abstract
Aucune valeur n'est associée à la propriété buildingMaterial
. En fait, vous ne pouvez pas lui attribuer une valeur, car un bâtiment non spécifique n'est composé d'aucun élément spécifique. Ainsi, comme l'indique le message d'erreur, vous pouvez faire précéder la déclaration de buildingMaterial
du mot clé abstract
pour indiquer qu'elle ne sera pas définie ici.
- Ajoutez le mot clé
abstract
à la définition de la variable.
abstract val buildingMaterial: String
- Exécutez votre code. Bien qu'il ne fasse rien, il est maintenant compilé sans aucune erreur.
- Créez une instance de
Dwelling
dans la fonctionmain()
, puis exécutez votre code.
val dwelling = Dwelling()
- Vous obtenez une erreur, car vous ne pouvez pas créer d'instance de la classe
Dwelling
abstraite.
Cannot create an instance of an abstract class
- Supprimez ce code incorrect.
À ce stade, votre code finalisé se présente comme suit :
abstract class Dwelling(){
abstract val buildingMaterial: String
}
Ajouter une propriété pour la capacité
Une autre propriété d'un logement est sa capacité, c'est-à-dire le nombre de personnes qui peuvent y vivre.
Tous les logements ont une capacité qui ne varie pas. Cependant, la capacité ne peut pas être définie dans la super-classe Dwelling
. Elle doit l'être dans des sous-classes pour des types de logement spécifiques.
- Dans
Dwelling
, ajoutez un entierabstract
val
appelécapacity
.
abstract val capacity: Int
Ajouter une propriété privée pour le nombre de résidents
Tous les logements auront un nombre de residents
(qui peut être inférieur ou égal à capacity
). Définissez donc la propriété residents
dans la super-classe Dwelling
pour que toutes les sous-classes en héritent et l'utilisent.
- Vous pouvez faire de
residents
un paramètre qui est transmis au constructeur de la classeDwelling
. La propriétéresidents
est de typevar
, car le nombre de résidents peut changer après la création de l'instance.
abstract class Dwelling(private var residents: Int) {
Notez que la propriété residents
est marquée avec le mot clé private
. "Private" est un modificateur de visibilité en langage Kotlin. Il fait en sorte que seule cette classe puisse voir (et utiliser) la propriété residents
. Elle n'est accessible depuis aucun autre emplacement de votre programme. Vous pouvez marquer des propriétés ou des méthodes avec le mot clé "Private". Si aucun modificateur de visibilité n'est spécifié, les propriétés et les méthodes sont publiques (public
) par défaut, et elles sont accessibles depuis d'autres parties de votre programme. Le nombre de personnes vivant dans un logement est généralement une information privée (contrairement aux informations sur les matériaux de construction ou sur la capacité du bâtiment). C'est donc une décision raisonnable.
En définissant à la fois la capacité (propriété capacity
) du logement et le nombre actuel de résidents (propriété residents
), vous pouvez créer une fonction hasRoom()
pour déterminer s'il y a de la place pour un autre résident. Vous pouvez définir et implémenter la fonction hasRoom()
dans la classe Dwelling
, car la formule permettant de calculer s'il y a de la place est la même pour tous les logements. Il y a de la place dans un logement (classe Dwelling
) si le nombre de residents
est inférieur à capacity
. La fonction doit renvoyer true
ou false
en fonction de cette comparaison.
- Ajoutez la fonction
hasRoom()
à la classeDwelling
.
fun hasRoom(): Boolean {
return residents < capacity
}
- Vous pouvez exécuter ce code, et aucune erreur ne doit se produire. Rien de visible n'est effectué pour le moment.
Votre code, une fois terminé, doit ressembler à ceci :
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
4. Créer des sous-classes
Créer une sous-classe SquareCabin
- Créez une classe nommée
SquareCabin
sous la classeDwelling
.
class SquareCabin
- Vous devez ensuite indiquer que
SquareCabin
est lié àDwelling
. Dans votre code, vous souhaitez indiquer queSquareCabin
s'étend à partir deDwelling
(ou qu'il s'agit d'une sous-classe deDwelling)
, carSquareCabin
fournit une implémentation pour les parties abstraites deDwelling
).
Pour indiquer cette relation d'héritage, ajoutez un signe deux-points (:
) après le nom de la classe SquareCabin
, puis un appel pour initialiser la classe parente Dwelling
. N'oubliez pas d'ajouter des parenthèses après le nom de la classe Dwelling
.
class SquareCabin : Dwelling()
- Lorsque vous effectuez une extension à partir d'une super-classe, vous devez transmettre les paramètres requis attendus par cette super-classe.
Dwelling
requiert le nombre deresidents
comme entrée. Vous pouvez transmettre un nombre fixe de résidents, comme3
.
class SquareCabin : Dwelling(3)
Cependant, vous souhaitez que votre programme soit plus flexible et autorise un nombre variable de résidents pour SquareCabins
. Vous allez donc définir residents
comme paramètre dans la définition de classe SquareCabin
. Ne déclarez pas residents
comme val,
, car vous réutilisez une propriété déjà déclarée dans la classe parente Dwelling
.
class SquareCabin(residents: Int) : Dwelling(residents)
- Exécutez votre code.
- Cela entraîne des erreurs. Voici un aperçu :
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling
Lorsque vous déclarez des variables et des fonctions abstraites, vous vous engagez en quelque sorte à leur attribuer des valeurs et des implémentations ultérieurement. Pour une variable, cela signifie que toute sous-classe de cette classe abstraite doit lui attribuer une valeur. Dans le cas d'une fonction, cela signifie que toute sous-classe doit implémenter le corps de la fonction.
Dans la classe Dwelling
, vous avez défini une variable abstract
buildingMaterial
. SquareCabin
est une sous-classe de Dwelling
. Elle doit donc fournir une valeur pour buildingMaterial
. Utilisez le mot clé override
pour indiquer que cette propriété a été définie dans une classe parente et qu'elle est sur le point d'être remplacée dans cette classe.
- Dans la classe
SquareCabin
, remplacez (override
) la propriétébuildingMaterial
et affectez-lui la valeur"Wood"
. - Faites de même pour
capacity
, en indiquant que six personnes peuvent vivre dans uneSquareCabin
.
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Votre code fini doit se présenter comme suit :
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Pour tester votre code, créez une instance de SquareCabin
dans votre programme.
Utiliser SquareCabin
- Insérez une fonction
main()
vide avant les définitions de classeDwelling
etSquareCabin
.
fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
- Dans la fonction
main()
, créez une instance deSquareCabin
nomméesquareCabin
avec six résidents. Ajoutez des instructions d'impression pour les matériaux de construction, la capacité et la fonctionhasRoom()
.
fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${squareCabin.capacity}")
println("Material: ${squareCabin.buildingMaterial}")
println("Has room? ${squareCabin.hasRoom()}")
}
Comme vous pouvez le voir, la fonction hasRoom()
n'a pas été définie dans la classe SquareCabin
, mais elle l'a été dans la classe Dwelling
. Comme SquareCabin
est une sous-classe de Dwelling
, la fonction hasRoom()
a été héritée sans frais. La fonction hasRoom()
peut maintenant être appelée sur toutes les instances de SquareCabin
, comme illustré dans l'extrait de code sous la forme squareCabin.hasRoom()
.
- Exécutez votre code. La sortie suivante devrait être affichée :
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
Vous avez créé squareCabin
avec 6
résidents, ce qui est égal à capacity
. hasRoom()
renvoie donc false
. Vous pouvez tester l'initialisation de SquareCabin
avec un plus petit nombre de residents
. Lorsque vous exécuterez à nouveau votre programme, hasRoom()
devrait alors renvoyer true
.
Utiliser le mot clé "with" pour simplifier votre code
Dans les instructions println()
, chaque fois que vous référencez une propriété ou une fonction de squareCabin
, vous devez répéter squareCabin.
. Cela devient répétitif et peut constituer une source d'erreurs lorsque vous copiez et collez des instructions d'impression.
Lorsque vous utilisez une instance spécifique d'une classe, et que vous devez accéder à plusieurs propriétés et fonctions de cette instance, vous pouvez demander d'effectuer toutes les opérations suivantes avec cet objet d'instance à l'aide d'une instruction with
. Commencez par le mot clé with
, suivi du nom de l'instance entre parenthèses, puis d'accolades contenant les opérations à effectuer.
with (instanceName) {
// all operations to do with instanceName
}
- Dans la fonction
main()
, modifiez vos instructions d'impression afin d'utiliserwith
. - Supprimez
squareCabin.
dans les instructions d'impression.
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
- Exécutez à nouveau votre code pour vous assurer qu'il fonctionne sans erreur et affiche la même sortie.
Square Cabin ============ Capacity: 6 Material: Wood Has room? false
Voici votre code finalisé :
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
Créer une sous-classe RoundHut
- Comme pour
SquareCabin
, ajoutez une autre sous-classe,RoundHut
, àDwelling
. - Remplacez
buildingMaterial
et attribuez-lui la valeur"Straw"
. - Remplacez la valeur
capacity
et définissez-la sur 4.
class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- Dans
main()
, créez une instance deRoundHut
avec trois résidents.
val roundHut = RoundHut(3)
- Ajoutez le code ci-dessous pour imprimer les informations sur
roundHut
.
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
- Exécutez votre code. La sortie pour l'ensemble du programme doit ressembler à ce qui suit :
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true
Vous disposez à présent d'une hiérarchie de classes semblable à celle-ci, avec Dwelling
comme classe racine, et SquareCabin
et RoundHut
comme sous-classes de Dwelling
.
Créer une sous-classe RoundTower
La dernière classe de cette hiérarchie est une tour ronde (Round Tower). Il s'agit d'une sorte de cabane ronde en pierre à plusieurs étages. Vous pouvez donc faire de RoundTower
une sous-classe de RoundHut
.
- Créez une classe
RoundTower
qui est une sous-classe deRoundHut
. Ajoutez le paramètreresidents
au constructeur deRoundTower
, puis transmettez-le au constructeur de la super-classeRoundHut
. - Remplacez
buildingMaterial
par"Stone"
. - Définissez la valeur
capacity
sur4
.
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Exécutez ce code. Vous obtenez alors un message d'erreur.
This type is final, so it cannot be inherited from
Cette erreur signifie que la classe RoundHut
ne peut pas être sous-classée (ou héritée). Par défaut, en langage Kotlin, les classes sont dites "finales" et ne peuvent pas être sous-classées. Vous n'êtes autorisé à hériter que de classes abstract
ou de classes marquées avec le mot clé open
. Vous devez donc marquer la classe RoundHut
avec le mot clé open
pour que l'héritage soit possible.
- Ajoutez le mot clé
open
au début de la déclarationRoundHut
.
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
- Dans
main()
, créez une instance deroundTower
et imprimez les informations la concernant.
val roundTower = RoundTower(4)
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
Voici le code complet :
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
- Exécutez votre code. Il devrait maintenant fonctionner sans erreur et générer la sortie suivante.
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Round Hut ========= Material: Straw Capacity: 4 Has room? true Round Tower ========== Material: Stone Capacity: 4 Has room? false
Ajouter plusieurs étages à RoundTower
RoundHut
est implicitement un bâtiment de plain-pied. Les tours ont généralement plusieurs étages.
Si l'on tient compte de la capacité, plus une tour compte d'étages, plus sa capacité devrait être importante.
Vous pouvez modifier RoundTower
pour qu'elle ait plusieurs étages et ajuster sa capacité en fonction du nombre de niveaux.
- Mettez à jour le constructeur
RoundTower
afin qu'il accepte un paramètre entier supplémentaireval floors
pour le nombre d'étages. Placez-le aprèsresidents
. Notez qu'il n'est pas nécessaire de transmettre ce paramètre au constructeurRoundHut
parent, carfloors
est défini ici dansRoundTower
, etRoundHut
n'a aucun étage (floors
).
class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
- Exécutez votre code. Une erreur se produit lors de la création de
roundTower
dans la méthodemain()
, car vous ne fournissez pas de nombre pour l'argumentfloors
. Vous pouvez ajouter l'argument manquant.
Vous pouvez également ajouter une valeur par défaut pour floors
dans la définition de classe de RoundTower
, comme indiqué ci-dessous. Ensuite, si aucune valeur n'est transmise au constructeur pour floors
, la valeur par défaut peut être utilisée pour créer l'instance d'objet.
- Dans votre code, ajoutez
= 2
après la déclaration defloors
pour lui affecter la valeur par défaut "2".
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
- Exécutez votre code. La compilation doit normalement être effectuée correctement, car
RoundTower(4)
crée maintenant une instance d'objetRoundTower
avec une valeur par défaut de 2 étages. - Dans la classe
RoundTower
, modifiez l'élémentcapacity
pour le multiplier par le nombre d'étages.
override val capacity = 4 * floors
- Exécutez votre code. Comme vous pouvez le constater, la capacité de
RoundTower
est maintenant de 8 pour 2 étages.
Voici votre code finalisé.
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
5. Modifier les classes de la hiérarchie
Calculer l'aire de plancher
Dans cet exercice, vous allez apprendre à déclarer une fonction abstraite dans une classe abstraite, puis à implémenter ses fonctionnalités dans les sous-classes.
Tous les logements ont une aire de plancher. Cependant, le mode de calcul de cette aire varie en fonction de la forme du logement.
Définir floorArea() dans la classe Dwelling
- Commencez par ajouter une fonction
floorArea()
abstract
à la classeDwelling
. Renvoyez unDouble
. Double est un type de données, tel queString
etInt
. Il est utilisé pour les nombres à virgule flottante, c'est-à-dire les nombres avec un signe décimal suivi d'une partie fractionnaire, comme 5,8793.
abstract fun floorArea(): Double
Toutes les méthodes abstraites définies dans une classe abstraite doivent être implémentées dans l'une de ses sous-classes. Avant de pouvoir exécuter votre code, vous devez implémenter floorArea()
dans les sous-classes.
Implémenter floorArea() pour SquareCabin
Comme pour buildingMaterial
et capacity
, puisque vous implémentez une fonction abstract
définie dans la classe parente, vous devez utiliser le mot clé override
.
- Dans la classe
SquareCabin
, commencez par le mot cléoverride
, suivi de l'implémentation réelle de la fonctionfloorArea()
, comme indiqué ci-dessous.
override fun floorArea(): Double {
}
- Renvoyez l'aire de plancher calculée. La formule pour calculer l'aire d'un carré est "côté fois côté". Pour connaître l'aire d'un rectangle, on multiplie la longueur par la largeur. Le corps de la fonction sera
return length * length
.
override fun floorArea(): Double {
return length * length
}
La longueur n'est pas une variable dans la classe et elle est différente pour chaque instance. Vous pouvez donc l'ajouter en tant que paramètre de constructeur pour la classe SquareCabin
.
- Modifiez la définition de classe de
SquareCabin
pour ajouter un paramètrelength
de typeDouble
. Déclarez la propriété en tant queval
, car la longueur d'un bâtiment ne change pas.
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
Dwelling
et, par conséquent, toutes ses sous-classes ont residents
comme argument de constructeur. Puisqu'il s'agit du premier argument du constructeur Dwelling
, il est recommandé de le définir également comme premier argument de tous les constructeurs de sous-classe et de placer les arguments dans le même ordre dans toutes les définitions de classe. Insérez donc le nouveau paramètre length
après le paramètre residents
.
- Dans
main()
, mettez à jour la création de l'instancesquareCabin
. Transmettez50.0
au constructeurSquareCabin
en tant quelength
.
val squareCabin = SquareCabin(6, 50.0)
- Dans l'instruction
with
poursquareCabin
, ajoutez une instruction d'impression pour l'aire de plancher.
println("Floor area: ${floorArea()}")
Votre code ne s'exécutera pas, car vous devez également implémenter floorArea()
dans RoundHut
.
Implémenter floorArea() pour RoundHut
De la même manière, vous allez implémenter l'aire de plancher pour RoundHut
. RoundHut
est également une sous-classe directe de Dwelling
. Vous devez donc utiliser le mot clé override
.
Pour calculer l'aire de plancher d'un logement de forme circulaire, on utilise la formule PI * rayon * rayon.
PI
est une valeur mathématique. Elle est définie dans une bibliothèque mathématique. Une bibliothèque est un ensemble prédéfini de fonctions et de valeurs définies en dehors d'un programme, et qu'un programme peut utiliser. Pour utiliser une valeur ou une fonction de bibliothèque, vous devez indiquer au compilateur votre intention de l'utiliser. Pour ce faire, importez la fonction ou la valeur dans votre programme. Pour utiliser PI
dans votre programme, vous devez importer kotlin.math.PI
.
- Importez
PI
à partir de la bibliothèque mathématique Kotlin. Placez-le en haut du fichier, avantmain()
.
import kotlin.math.PI
- Implémentez la fonction
floorArea()
pourRoundHut
.
override fun floorArea(): Double {
return PI * radius * radius
}
Avertissement : Si vous n'importez pas kotlin.math.PI, une erreur s'affiche. Vous devez donc importer cette bibliothèque avant de l'utiliser. Vous pouvez également écrire la version complète de PI, comme dans kotlin.math.PI * rayon * rayon. Ensuite, l'instruction d'importation n'est pas nécessaire.
- Mettez à jour le constructeur
RoundHut
pour transmettre leradius
.
open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
- Dans
main()
, mettez à jour l'initialisation deroundHut
en transmettant un élémentradius
de10.0
au constructeurRoundHut
.
val roundHut = RoundHut(3, 10.0)
- Ajoutez une instruction d'impression dans l'instruction
with
pourroundHut
.
println("Floor area: ${floorArea()}")
Implémenter floorArea() pour RoundTower
L'exécution de votre code échoue et le message d'erreur suivant est affiché :
Error: No value passed for parameter 'radius'
Dans RoundTower
, pour que votre programme soit compilé, il n'est pas nécessaire d'implémenter floorArea()
, car il est hérité de RoundHut
. Cependant, vous devez mettre à jour la définition de la classe RoundTower
pour qu'elle ait également le même argument radius
que sa classe parente RoundHut
.
- Modifiez le constructeur de RoundTower pour qu'il accepte également
radius
. Placezradius
aprèsresidents
et avantfloors
. Il est recommandé de lister les variables avec des valeurs par défaut à la fin. N'oubliez pas de transmettreradius
au constructeur de la classe parente.
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
- Mettez à jour l'initialisation de
roundTower
dansmain()
.
val roundTower = RoundTower(4, 15.5)
- Ajoutez également une instruction d'impression qui appelle
floorArea()
.
println("Floor area: ${floorArea()}")
- Vous pouvez maintenant exécuter votre code.
- Notez que le calcul de
RoundTower
est incorrect, car il est hérité deRoundHut
et ne prend pas en compte le nombre defloors
. - Dans
RoundTower
, indiquezoverride floorArea()
afin de lui attribuer une implémentation différente qui multiplie la surface par le nombre d'étages. Comme vous pouvez le constater, vous pouvez définir une fonction dans une classe abstraite (Dwelling
), l'implémenter dans une sous-classe (RoundHut
), puis la remplacer à nouveau dans une sous-classe de la sous-classe (RoundTower
). C'est le compromis parfait : vous héritez de la fonctionnalité de votre choix et pouvez remplacer celle dont vous n'avez pas besoin.
override fun floorArea(): Double {
return PI * radius * radius * floors
}
Ce code fonctionne, mais il existe un moyen d'éviter de répéter celui qui figure déjà dans la classe parente RoundHut
. Vous pouvez appeler la fonction floorArea()
à partir de la classe parente RoundHut
pour renvoyer PI * radius * radius
. Multipliez ensuite ce résultat par le nombre de floors
.
- Dans
RoundTower
, mettez à jourfloorArea()
de manière à utiliser l'implémentation de super-classe defloorArea()
. Utilisez le mot clésuper
pour appeler la fonction définie dans le parent.
override fun floorArea(): Double {
return super.floorArea() * floors
}
- Exécutez à nouveau votre code.
RoundTower
génère alors la surface correcte pour plusieurs étages.
Voici votre code finalisé :
import kotlin.math.PI
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
class SquareCabin(residents: Int,
val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
override fun floorArea(): Double {
return length * length
}
}
open class RoundHut(residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
Un résultat semblable aux lignes suivantes doit s'afficher :
Square Cabin ============ Capacity: 6 Material: Wood Has room? false Floor area: 2500.0 Round Hut ========= Material: Straw Capacity: 4 Has room? true Floor area: 314.1592653589793 Round Tower ========== Material: Stone Capacity: 8 Has room? true Floor area: 1509.5352700498956
Autoriser un nouveau résident à obtenir une chambre
Ajoutez la possibilité pour un nouveau résident d'obtenir une chambre avec une fonction getRoom()
qui augmente le nombre de résidents d'une unité. Comme cette logique est la même pour tous les logements, vous pouvez implémenter la fonction dans Dwelling
, ce qui la rend disponible pour toutes les sous-classes et leurs enfants. Simple et efficace !
Remarques :
- Utilisez une instruction
if
qui n'ajoute un résident que s'il reste de la capacité. - Imprimez un message pour le résultat.
- Vous pouvez utiliser
residents++
comme raccourci pourresidents = residents + 1
afin d'ajouter 1 à la variableresidents
.
- Implémentez la fonction
getRoom()
dans la classeDwelling
.
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
- Ajoutez des instructions d'impression au bloc d'instructions
with
pourroundHut
afin d'observer ce qui se passe lorsquegetRoom()
ethasRoom()
sont utilisés ensemble.
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
Sortie pour ces instructions d'impression :
Has room? true You got a room! Has room? false Sorry, at capacity and no rooms left.
Pour en savoir plus, consultez le code de solution.
Installer un tapis dans un logement de forme circulaire
Supposons que, pour installer un tapis dans votre RoundHut
ou RoundTower
, vous deviez connaître la longueur d'un côté. Placez la fonction dans RoundHut
pour qu'elle soit disponible pour tous les logements de forme circulaire.
- Commencez par importer la fonction
sqrt()
à partir de la bibliothèquekotlin.math
.
import kotlin.math.sqrt
- Implémentez la fonction
calculateMaxCarpetLength()
dans la classeRoundHut
. La formule permettant de calculer la longueur du tapis carré pouvant tenir dans un cercle estsqrt(2) * radius
. Cela est expliqué dans le schéma ci-dessus.
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
Transmettez une valeur Double
, 2.0
, à la fonction mathématique sqrt(2.0)
, car le type renvoyé de la fonction est Double
et non Integer
.
- La méthode
calculateMaxCarpetLength()
peut maintenant être appelée sur les instancesRoundHut
etRoundTower
. Ajoutez des instructions d'impression àroundHut
etroundTower
dans la fonctionmain()
.
println("Carpet Length: ${calculateMaxCarpetLength()}")
Pour en savoir plus, consultez le code de solution.
Félicitations ! Vous avez créé une hiérarchie de classes complète avec des propriétés et des fonctions. Vous disposez à présent de toutes les connaissances nécessaires pour créer des classes encore plus efficaces.
6. Code de solution
Voici le code de solution complet pour cet atelier de programmation, y compris les commentaires.
/**
* Program that implements classes for different kinds of dwellings.
* Shows how to:
* Create class hierarchy, variables and functions with inheritance,
* abstract class, overriding, and private vs. public variables.
*/
import kotlin.math.PI
import kotlin.math.sqrt
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
println("Carpet size: ${calculateMaxCarpetLength()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Carpet Length: ${calculateMaxCarpetLength()}")
}
}
/**
* Defines properties common to all dwellings.
* All dwellings have floorspace,
* but its calculation is specific to the subclass.
* Checking and getting a room are implemented here
* because they are the same for all Dwelling subclasses.
*
* @param residents Current number of residents
*/
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
/**
* Calculates the floor area of the dwelling.
* Implemented by subclasses where shape is determined.
*
* @return floor area
*/
abstract fun floorArea(): Double
/**
* Checks whether there is room for another resident.
*
* @return true if room available, false otherwise
*/
fun hasRoom(): Boolean {
return residents < capacity
}
/**
* Compares the capacity to the number of residents and
* if capacity is larger than number of residents,
* add resident by increasing the number of residents.
* Print the result.
*/
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
}
/**
* A square cabin dwelling.
*
* @param residents Current number of residents
* @param length Length
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
/**
* Calculates floor area for a square dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return length * length
}
}
/**
* Dwelling with a circular floorspace
*
* @param residents Current number of residents
* @param radius Radius
*/
open class RoundHut(
residents: Int, val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
/**
* Calculates floor area for a round dwelling.
*
* @return floor area
*/
override fun floorArea(): Double {
return PI * radius * radius
}
/**
* Calculates the max length for a square carpet
* that fits the circular floor.
*
* @return length of square carpet
*/
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
}
/**
* Round tower with multiple stories.
*
* @param residents Current number of residents
* @param radius Radius
* @param floors Number of stories
*/
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
// Capacity depends on the number of floors.
override val capacity = floors * 4
/**
* Calculates the total floor area for a tower dwelling
* with multiple stories.
*
* @return floor area
*/
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
7. Résumé
Dans cet atelier de programmation, vous avez appris ce qui suit :
- Créer une hiérarchie de classes, c'est-à-dire une arborescence de classes dans laquelle les enfants héritent des fonctionnalités des classes parentes. Les sous-classes héritent des propriétés et des fonctions.
- Créer une classe
abstract
dont les sous-classes doivent encore implémenter certaines fonctionnalités. Dès lors, une classeabstract
ne peut pas être instanciée. - Créer des sous-classes d'une classe
abstract
. - Utiliser le mot clé
override
pour remplacer des propriétés et des fonctions dans les sous-classes. - Utiliser le mot clé
super
pour référencer des propriétés et des fonctions dans la classe parente. - Rendre une classe
open
afin de pouvoir la sous-classer. - Rendre une propriété
private
pour qu'elle ne puisse être utilisée que dans la classe. - Utiliser la construction
with
pour effectuer plusieurs appels sur la même instance d'objet. - Importer une fonctionnalité à partir de la bibliothèque
kotlin.math
.
8. En savoir plus
- Héritage
- Classes et héritage
- Constructeurs
- Héritage (abstract, open, override, private)
with
(définition formelle)