1. Avant de commencer
Cet atelier de programmation vous explique comment utiliser des classes et des objets en langage Kotlin.
Les classes fournissent des plans à partir desquels vous pouvez créer des objets. Un objet est une instance d'une classe composée de données spécifiques à cet objet. Les objets et les instances de classe peuvent être utilisés de façon interchangeable.
À titre de comparaison, imaginez que vous construisiez une maison. Une classe est comparable à un plan d'architecte. Le plan n'est pas la maison, mais un ensemble d'instructions sur la façon de la construire. La maison est l'objet qui est construit à partir du plan.
Tout comme le plan d'une maison spécifie plusieurs pièces, ayant chacune sa propre conception et sa propre fonction, chaque classe possède une conception et une fonction qui lui sont propres. Pour apprendre à concevoir vos classes, vous devez vous familiariser avec la programmation orientée objet, un framework qui vous apprend à placer les données, la logique et le comportement dans des objets.
La programmation orientée objet vous aide à simplifier des problèmes concrets complexes en les scindant en objets plus petits. Quatre concepts de base sont associés à ce type de programmation. Vous les découvrirez de manière détaillée dans la suite de cet atelier de programmation :
- Encapsulation. Encapsule les propriétés et méthodes associées qui effectuent des actions sur ces propriétés dans une classe. Prenons l'exemple de votre téléphone mobile. Il "encapsule" un appareil photo, un écran, des cartes mémoire, ainsi que plusieurs autres composants matériels et logiciels. Vous n'avez pas à vous soucier de la façon dont les composants sont câblés en interne.
- Abstraction. Il s'agit d'une extension de l'encapsulation. L'idée est de masquer autant que possible la logique d'implémentation interne. Par exemple, pour prendre une photo avec votre téléphone mobile, il vous suffit d'ouvrir l'application Appareil photo, de pointer votre téléphone vers la scène que vous souhaitez capturer, puis de cliquer sur un bouton pour prendre la photo. Nul besoin de savoir comment est construite l'application Appareil photo ni de comprendre comment fonctionne réellement l'appareil photo de votre téléphone mobile. En bref, le mécanisme interne de l'application Appareil photo et la façon dont l'appareil photo mobile capture les photos sont rendus abstraits pour vous permettre d'effectuer les tâches importantes.
- Héritage. Permet de construire une classe sur la base des caractéristiques et du comportement d'autres classes en établissant une relation parent-enfant. Par exemple, plusieurs fabricants produisent des appareils mobiles équipés de l'OS Android, mais l'interface utilisateur est différente pour chacun d'eux. En d'autres termes, les fabricants héritent de la fonctionnalité de l'OS Android et y ajoutent leurs personnalisations.
- Polymorphisme. Le mot est une adaptation de la racine grecque poly-, qui signifie "plusieurs", et de -morphisme, qui signifie forme. Le polymorphisme est la capacité à utiliser différents objets d'une façon commune. Par exemple, lorsque vous connectez une enceinte Bluetooth à votre téléphone mobile, la seule chose que le téléphone a besoin de savoir, c'est qu'un appareil peut lire du contenu audio via le Bluetooth. Cependant, vous pouvez faire votre choix parmi toute une gamme d'enceintes Bluetooth et votre téléphone n'a pas besoin de savoir comment utiliser spécifiquement chacune d'elles.
Pour terminer, vous découvrirez les délégués de propriété, qui fournissent du code réutilisable pour gérer des valeurs de propriété avec une syntaxe concise. Dans cet atelier de programmation, vous apprendrez ces concepts en créant une structure de classe pour une application de maison connectée.
Conditions préalables
- Vous savez comment ouvrir, modifier et exécuter du code dans Kotlin Playground.
- Vous connaissez les bases de la programmation Kotlin, y compris les variables, les fonctions, ainsi que les fonctions
println()
etmain()
.
Points abordés
- Présentation de la programmation orientée objet
- Présentation des classes
- Définir une classe avec des constructeurs, des fonctions et des propriétés
- Instancier un objet
- Définition de l'héritage
- Différence entre les relations IS-A et HAS-A
- Remplacer des propriétés et des fonctions
- Présentation des modificateurs de visibilité
- Présentation du délégué et savoir utiliser le délégué
by
Objectifs de l'atelier
- Créer une structure de classe pour la maison connectée
- Créer des classes représentant des appareils connectés, comme une smart TV et une ampoule connectée
Ce dont vous avez besoin
- Un ordinateur avec accès à Internet et un navigateur Web
2. Définir une classe
Lorsque vous définissez une classe, vous spécifiez les propriétés et les méthodes que tous les objets de cette classe doivent posséder.
Une définition de classe commence par le mot clé class
, suivi d'un nom et d'une série d'accolades. La partie de la syntaxe située avant l'accolade ouvrante est également appelée en-tête de classe. Vous pouvez spécifier les propriétés et les fonctions de la classe entre accolades. Vous en apprendrez bientôt plus sur les propriétés et les fonctions. Le schéma ci-dessous présente la syntaxe d'une définition de classe :
Voici les conventions d'attribution de noms recommandées pour une classe :
- Vous pouvez choisir n'importe quel nom de classe, mais n'utilisez pas de mots clés Kotlin, tels que
fun
. - Le nom de la classe est écrit au format PascalCase. Chaque mot commence donc par une lettre majuscule et il n'y a pas d'espace entre les mots, comme dans SmartDevice.
Une classe comprend trois parties principales :
- Propriétés. Variables spécifiant les attributs des objets de la classe.
- Méthodes. Fonctions contenant les comportements et les actions de la classe.
- Constructeurs. Fonction membre spéciale qui crée des instances de la classe dans tout le programme dans lequel elle est définie.
Ce n'est pas la première fois que vous travaillez avec des classes. Dans les précédents ateliers de programmation, vous avez découvert les types de données, tels que Int
, Float
, String
et Double
. Ces types de données sont définis comme des classes en langage Kotlin. Lorsque vous définissez une variable comme indiqué dans cet extrait de code, vous créez un objet de la classe Int
, qui est instancié avec une valeur 1
:
val number: Int = 1
Définissez une classe SmartDevice
:
- Dans Kotlin Playground, remplacez le contenu par une fonction
main()
vide :
fun main() {
}
- Sur la ligne qui précède la fonction
main()
, définissez une classeSmartDevice
avec un corps contenant un commentaire//
empty
body
:
class SmartDevice {
// empty body
}
fun main() {
}
3. Créer une instance d'une classe
Comme nous l'avons vu précédemment, une classe est un plan d'un objet. L'environnement d'exécution Kotlin utilise la classe (ou plan) pour créer un objet de ce type. Avec la classe SmartDevice
, vous avez le plan d'un appareil connecté. Pour intégrer un appareil connecté réel à votre programme, vous devez créer une instance d'objet SmartDevice
. Comme vous pouvez le voir ci-dessous, la syntaxe d'instanciation commence par le nom de la classe, suivi d'une paire de parenthèses :
Pour utiliser un objet, vous devez le créer et l'affecter à une variable, de la même manière que vous définissez une variable. Vous utilisez le mot clé val
pour créer une variable non modifiable et le mot clé var
pour une variable modifiable. Le mot clé val
ou var
est suivi du nom de la variable, d'un opérateur d'affectation =
, puis de l'instanciation de l'objet de classe. La syntaxe est illustrée dans le schéma ci-dessous :
Instanciez la classe SmartDevice
en tant qu'objet :
- Dans la fonction
main()
, utilisez le mot cléval
pour créer une variable nomméesmartTvDevice
et l'initialiser en tant qu'instance de la classeSmartDevice
:
fun main() {
val smartTvDevice = SmartDevice()
}
4. Définir des méthodes de classe
Dans le module 1, vous avez appris ce qui suit :
- La définition d'une fonction utilise le mot clé
fun
, suivi d'une série de parenthèses et d'accolades. Les accolades contiennent du code. Il s'agit des instructions requises pour exécuter une tâche. - L'appel d'une fonction entraîne l'exécution de tout le code qu'elle contient.
Les actions que la classe peut effectuer y sont définies en tant que fonctions. Imaginons, par exemple, que vous possédiez un appareil connecté (une smart TV ou un système d'éclairage connecté) que vous pouvez allumer et éteindre à l'aide de votre téléphone mobile. L'appareil connecté est converti en classe SmartDevice
en programmation, et l'action permettant de l'allumer ou de l'éteindre est représentée par les fonctions turnOn()
et turnOff()
, qui activent le comportement "allumé" et "éteint".
La syntaxe permettant de définir une fonction dans une classe est identique à celle décrite précédemment. La seule différence réside dans le fait que la fonction est placée dans le corps de la classe. Lorsque vous définissez une fonction dans le corps de la classe, elle est désignée sous le nom de fonction membre ou méthode. Elle représente le comportement de la classe. Dans la suite de cet atelier de programmation, les fonctions seront appelées "méthodes" chaque fois qu'elles apparaîtront dans le corps d'une classe.
Définissez une méthode turnOn()
et turnOff()
dans la classe SmartDevice
:
- Dans le corps de la classe
SmartDevice
, définissez une méthodeturnOn()
avec un corps vide :
class SmartDevice {
fun turnOn() {
}
}
- Dans le corps de la méthode
turnOn()
, ajoutez une instructionprintln()
, puis transmettez-lui une chaîne"Smart
device
is
turned
on."
:
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
}
- Après la méthode
turnOn()
, ajoutez une méthodeturnOff()
qui imprime une chaîne"Smart
device
is
turned
off."
:
class SmartDevice {
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
Appeler une méthode sur un objet
Jusqu'à présent, vous avez défini une classe servant de plan pour un appareil connecté, créé une instance de la classe et affecté cette instance à une variable. Vous allez maintenant utiliser les méthodes de la classe SmartDevice
pour allumer et éteindre l'appareil.
Pour appeler une méthode dans une classe, la procédure est la même que celle utilisée pour appeler d'autres fonctions à partir de la fonction main()
dans l'atelier de programmation précédent. Par exemple, si vous devez appeler la méthode turnOff()
à partir de turnOn()
, vous pouvez écrire un extrait de code semblable à celui-ci :
class SmartDevice {
fun turnOn() {
// A valid use case to call the turnOff() method could be to turn off the TV when available power doesn't meet the requirement.
turnOff()
...
}
...
}
Pour appeler une méthode de classe en dehors de la classe, commencez par l'objet de classe suivi de l'opérateur .
, du nom de la fonction et d'une paire de parenthèses. Le cas échéant, les parenthèses contiennent les arguments requis par la méthode. La syntaxe est illustrée dans le schéma ci-dessous :
Appelez les méthodes turnOn()
et turnOff()
sur l'objet :
- Appelez la méthode
turnOn()
dans la fonctionmain()
sur la ligne située après la variablesmartTvDevice
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
}
- Sur la ligne qui suit la méthode
turnOn()
, appelez la méthodeturnOff()
:
fun main() {
val smartTvDevice = SmartDevice()
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- Exécutez le code.
La sortie est la suivante :
Smart device is turned on. Smart device is turned off.
5. Définir les propriétés de classe
Dans le module 1, vous avez appris ce qu'était une variable, à savoir un conteneur destiné à des éléments de données individuels. Vous avez également appris à créer une variable en lecture seule avec le mot clé val
et une variable modifiable avec le mot clé var
.
Alors que les méthodes définissent les actions qu'une classe peut effectuer, les propriétés définissent ses caractéristiques ou attributs de données. Par exemple, un appareil connecté possède les propriétés suivantes :
- Nom. Nom de l'appareil.
- Catégorie. Type d'appareil connecté (par exemple, divertissement, ou objet connecté pour la maison ou la cuisine).
- État de l'appareil. Indique si l'appareil est allumé, éteint, en ligne ou hors connexion. L'appareil est considéré comme étant en ligne lorsqu'il est connecté à Internet. Sinon, il est considéré comme étant hors connexion.
Les propriétés sont essentiellement des variables définies dans le corps de la classe et non dans celui de la fonction. Cela signifie que la syntaxe utilisée pour définir les propriétés et les variables est identique. Vous définissez une propriété non modifiable avec le mot clé val
et une propriété modifiable avec le mot clé var
.
Implémentez les caractéristiques mentionnées ci-dessus en tant que propriétés de la classe SmartDevice
:
- Sur la ligne située avant la méthode
turnOn()
, définissez la propriéténame
, puis affectez-la à une chaîne"Android
TV"
:
class SmartDevice {
val name = "Android TV"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
- Sur la ligne située après la propriété
name
, définissez la propriétécategory
et affectez-la à une chaîne"Entertainment"
, puis définissez une propriétédeviceStatus
et affectez-la à une chaîne"online"
:
class SmartDevice {
val name = "Android TV"
val category = "Entertainment"
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
- Sur la ligne située après la variable
smartTvDevice
, appelez la fonctionprintln()
, puis transmettez-lui une chaîne"Device
name
is:
${smartTvDevice.name}"
:
fun main() {
val smartTvDevice = SmartDevice()
println("Device name is: ${smartTvDevice.name}")
smartTvDevice.turnOn()
smartTvDevice.turnOff()
}
- Exécutez le code.
La sortie est la suivante :
Device name is: Android TV Smart device is turned on. Smart device is turned off.
Fonctions getter et setter dans les propriétés
Les propriétés offrent davantage de possibilités que les variables. Imaginons que vous créiez une structure de classe représentant une smart TV. Une des actions que vous effectuez fréquemment consiste à augmenter et baisser le volume. Pour représenter cette action en programmation, vous pouvez créer une propriété nommée speakerVolume
qui contient le niveau de volume actuel réglé sur l'enceinte du téléviseur. Cependant, il existe une plage de réglage du volume comprise entre 0 et 100. Pour vous assurer que la propriété speakerVolume
ne dépasse jamais 100 ou n'est jamais inférieure à 0, vous pouvez écrire une fonction setter. Lorsque vous modifiez la valeur de la propriété, vous devez vérifier si elle est bien comprise entre 0 et 100. Prenons un autre exemple : supposons que le nom doive toujours être en majuscules. Vous pouvez implémenter une fonction getter pour convertir la propriété name
en majuscules.
Avant d'aller plus loin dans l'implémentation de ces propriétés, vous devez comprendre toute la syntaxe utilisée pour les déclarer. La syntaxe complète permettant de définir une propriété modifiable commence par la définition de la variable, suivie des fonctions facultatives get()
et set()
. La syntaxe est illustrée dans le schéma ci-dessous :
Lorsque vous ne définissez pas les fonctions getter et setter d'une propriété, le compilateur Kotlin les crée en interne. Par exemple, si vous utilisez le mot clé var
pour définir une propriété speakerVolume
et lui affecter une valeur 2
, le compilateur génère automatiquement les fonctions getter et setter, comme vous pouvez le voir dans l'extrait de code ci-dessous :
var speakerVolume = 2
get() = field
set(value) {
field = value
}
Vous ne verrez pas ces lignes dans votre code, car le compilateur les ajoute en arrière-plan.
La syntaxe complète d'une propriété non modifiable présente deux différences :
- Elle commence par le mot clé
val
. - Les variables de type
val
sont en lecture seule. Elles ne comportent donc pas de fonctionsset()
.
Les propriétés Kotlin utilisent un champ de stockage pour conserver une valeur en mémoire. Un champ de stockage est essentiellement une variable de classe définie en interne dans les propriétés. Un champ de stockage est limité à une propriété, ce qui signifie que vous ne pouvez y accéder qu'à l'aide des fonctions de propriété get()
ou set()
.
Pour lire la valeur de la propriété dans la fonction get()
ou mettre à jour la valeur dans la fonction set()
, vous devez utiliser le champ de stockage de la propriété. Il est généré automatiquement par le compilateur Kotlin et référencé avec un identifiant field
.
Par exemple, si vous souhaitez mettre à jour la valeur de la propriété dans la fonction set()
, vous devez utiliser le paramètre de la fonction set()
(appelé paramètre value
) et l'affecter à la variable field
, comme vous pouvez le voir dans l'extrait de code ci-dessous :
var speakerVolume = 2
set(value) {
field = value
}
Par exemple, pour vous assurer que la valeur affectée à la propriété speakerVolume
est comprise entre 0 et 100, vous pouvez implémenter la fonction setter, comme illustré dans l'extrait de code ci-dessous :
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
Les fonctions set()
vérifient si la valeur Int
est comprise entre 0 et 100 en utilisant le mot clé in
, suivi de la plage de valeurs. Si la valeur se trouve dans la plage attendue, la valeur field
est mise à jour. Sinon, la valeur de la propriété ne change pas.
Vous allez inclure cette propriété dans une classe dans la section Implémenter une relation entre les classes de cet atelier. Il n'est donc pas nécessaire d'ajouter la fonction setter au code maintenant.
6. Définir un constructeur
L'objectif principal du constructeur est de spécifier comment les objets de la classe sont créés. En d'autres termes, les constructeurs initialisent un objet et font en sorte qu'il soit prêt à l'emploi. C'est ce que vous avez fait lors de l'instanciation de l'objet. Le code à l'intérieur du constructeur s'exécute lorsque l'objet de la classe est instancié. Vous pouvez définir un constructeur avec ou sans paramètres.
Constructeur par défaut
Un constructeur par défaut est un constructeur sans paramètres. Vous pouvez définir un constructeur de ce type comme indiqué dans cet extrait de code :
class SmartDevice constructor() {
...
}
La concision est l'une des caractéristiques du langage Kotlin. Vous pouvez donc supprimer le mot clé constructor
si le constructeur ne contient ni annotations ni modificateurs de visibilité (nous reviendrons sur ce point dans une autre section). Vous pouvez également supprimer les parenthèses si le constructeur ne comporte aucun paramètre, comme illustré dans l'extrait de code ci-dessous :
class SmartDevice {
...
}
Le compilateur Kotlin génère automatiquement le constructeur par défaut. Ce constructeur n'est pas visible dans votre code, car le compilateur l'ajoute en arrière-plan.
Définir un constructeur paramétré
Dans la classe SmartDevice
, les propriétés name
et category
ne sont pas modifiables. Vous devez vous assurer que toutes les instances de la classe SmartDevice
initialisent les propriétés name
et category
. Dans l'implémentation actuelle, les valeurs des propriétés name
et category
sont codées en dur. Cela signifie que tous les appareils connectés sont nommés avec la chaîne "Android
TV"
et classés avec la chaîne "Entertainment"
.
Pour maintenir l'immuabilité tout en évitant les valeurs codées en dur, utilisez un constructeur paramétré pour les initialiser :
- Dans la classe
SmartDevice
, déplacez les propriétésname
etcategory
vers le constructeur sans affecter de valeurs par défaut :
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
fun turnOn() {
println("Smart device is turned on.")
}
fun turnOff() {
println("Smart device is turned off.")
}
}
Le constructeur accepte à présent des paramètres pour configurer ses propriétés. Par conséquent, la façon d'instancier un objet pour une classe de ce type change également. Le schéma ci-dessous illustre la syntaxe complète pour instancier un objet :
Voici la représentation du code :
SmartDevice("Android TV", "Entertainment")
Les deux arguments du constructeur sont des chaînes. Il n'est pas facile de déterminer à quel paramètre la valeur doit être affectée. Pour résoudre ce problème, de la même manière que vous avez transmis des arguments de fonction, vous pouvez créer un constructeur avec des arguments nommés, comme illustré dans cet extrait de code :
SmartDevice(name = "Android TV", category = "Entertainment")
En langage Kotlin, il existe deux principaux types de constructeurs :
- Constructeur principal. Une classe ne peut avoir qu'un seul constructeur principal, qui est défini dans l'en-tête de la classe. Un constructeur principal peut être un constructeur par défaut ou paramétré. Le constructeur principal ne possède pas de corps, ce qui signifie qu'il ne peut pas contenir de code.
- Constructeur secondaire. Une classe peut comporter plusieurs constructeurs secondaires. Vous pouvez définir le constructeur secondaire avec ou sans paramètres. Le constructeur secondaire peut initialiser la classe et a un corps qui peut contenir la logique d'initialisation. Si la classe comporte un constructeur principal, chaque constructeur secondaire doit l'initialiser.
Vous pouvez utiliser le constructeur principal pour initialiser les propriétés de l'en-tête de classe. Les arguments transmis au constructeur sont affectés aux propriétés. La syntaxe permettant de définir un constructeur principal commence par le nom de la classe, suivi du mot clé constructor
et d'une paire de parenthèses. Les parenthèses contiennent les paramètres du constructeur principal. S'il y a plusieurs paramètres, des virgules sont utilisées pour séparer leurs définitions. Le schéma ci-dessous illustre la syntaxe complète permettant de définir un constructeur principal :
Le constructeur secondaire est inclus dans le corps de la classe et sa syntaxe se compose de trois parties :
- Déclaration du constructeur secondaire. La définition du constructeur secondaire commence par le mot clé
constructor
, suivi de parenthèses. Le cas échéant, les parenthèses contiennent les paramètres requis par le constructeur secondaire. - Initialisation du constructeur principal. L'initialisation commence par le signe deux-points, suivi du mot clé
this
et d'une paire de parenthèses. Le cas échéant, les parenthèses contiennent les paramètres requis par le constructeur principal. - Corps du constructeur secondaire. L'initialisation du constructeur principal est suivie d'une paire d'accolades contenant le corps du constructeur secondaire.
La syntaxe est illustrée dans le schéma ci-dessous :
Imaginons que vous souhaitiez intégrer une API développée par un fournisseur d'appareils connectés. Cependant, l'API renvoie un code d'état de type Int
pour indiquer l'état initial de l'appareil. L'API renvoie une valeur 0
si l'appareil est hors connexion et une valeur 1
s'il est en ligne. Pour toute autre valeur entière, l'état est considéré comme inconnu. Vous pouvez créer un constructeur secondaire dans la classe SmartDevice
afin de convertir le paramètre statusCode
en une représentation sous forme de chaîne, comme vous pouvez le voir dans l'extrait de code suivant :
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
constructor(name: String, category: String, statusCode: Int) : this(name, category) {
deviceStatus = when (statusCode) {
0 -> "offline"
1 -> "online"
else -> "unknown"
}
}
...
}
7. Implémenter une relation entre les classes
Le concept d'héritage vous permet de créer une classe en vous appuyant sur les caractéristiques et le comportement d'une autre classe. Il s'agit d'un mécanisme efficace qui vous aide à écrire du code réutilisable et à établir des relations entre les classes.
On trouve aujourd'hui dans le commerce de nombreux appareils connectés, tels que des smart TV, des systèmes d'éclairage et des interrupteurs connectés. Lorsque vous représentez des appareils connectés en programmation, ils partagent certaines propriétés communes, telles que le nom, la catégorie et l'état. Ils ont également des comportements communs, comme la possibilité d'être allumés et éteints.
Cependant, la façon d'allumer ou d'éteindre chaque appareil connecté est différente. Par exemple, pour allumer un téléviseur, vous devrez peut-être allumer l'écran, puis régler la dernière chaîne et le dernier niveau de volume connus. En revanche, pour allumer une lumière, il vous suffira peut-être d'augmenter ou de diminuer la luminosité.
De plus, chaque appareil connecté permet d'effectuer d'autres fonctions et actions. Par exemple, sur un téléviseur, vous pouvez régler le volume et changer de chaîne. Dans le cas d'un système d'éclairage, vous pouvez régler la luminosité ou la couleur.
En bref, tous les appareils connectés présentent des fonctionnalités différentes, mais ils partagent aussi des caractéristiques communes. Vous pouvez soit dupliquer ces caractéristiques communes sur chacune des classes d'appareils connectés, soit rendre le code réutilisable avec le mécanisme d'héritage.
Pour ce faire, vous devez créer une classe parente SmartDevice
, puis définir ces propriétés et comportements communs. Vous pouvez ensuite créer des classes enfants, telles que SmartTvDevice
et SmartLightDevice
, qui héritent des propriétés de la classe parente.
Dans le jargon de la programmation, on dit que les classes SmartTvDevice
et SmartLightDevice
étendent la classe parente SmartDevice
. La classe parente est également appelée super-classe et la classe enfant, sous-classe. La relation entre ces classes est illustrée dans le schéma suivant :
Cependant, en langage Kotlin, toutes les classes sont dites "finales" par défaut, ce qui signifie que vous ne pouvez pas les étendre. Vous devez donc définir des relations entre elles.
Définissez la relation entre la super-classe SmartDevice
et ses sous-classes :
- Dans la super-classe
SmartDevice
, ajoutez un mot cléopen
avant le mot cléclass
pour le rendre extensible :
open class SmartDevice(val name: String, val category: String) {
...
}
Le mot clé open
informe le compilateur que cette classe est extensible, de sorte que d'autres classes puissent maintenant l'étendre.
La syntaxe utilisée pour créer une sous-classe commence par la création de l'en-tête de classe, comme vous l'avez fait jusqu'à présent. La parenthèse fermante du constructeur est suivie d'une espace, du signe deux-points, d'une autre espace, du nom de la super-classe et enfin d'une paire de parenthèses. Si nécessaire, les parenthèses contiennent les paramètres requis par le constructeur de super-classe. La syntaxe est illustrée dans le schéma ci-dessous :
- Créez une sous-classe
SmartTvDevice
qui étend la super-classeSmartDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
La définition constructor
de la sous-classe SmartTvDevice
ne précise pas si les propriétés sont modifiables ou non. Cela signifie que deviceName
et deviceCategory
sont de simples paramètres constructor
plutôt que des propriétés de classe. Vous ne pourrez pas les utiliser dans la classe, mais vous les transmettrez simplement au constructeur de super-classe.
- Dans le corps de la sous-classe
SmartTvDevice
, ajoutez la propriétéspeakerVolume
que vous avez créée lorsque vous avez étudié les fonctions getter et setter :
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
}
- Définissez une propriété
channelNumber
affectée à une valeur1
avec une fonction setter qui spécifie une plage0..200
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
}
- Définissez une méthode
increaseSpeakerVolume()
qui augmente le volume et imprime une chaîne"Speaker
volume
increased
to
$speakerVolume."
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
}
- Ajoutez une méthode
nextChannel()
qui augmente le numéro de chaîne et imprime une chaîne"Channel
number
increased
to
$channelNumber."
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
}
- Sur la ligne située après la sous-classe
SmartTvDevice
, définissez une sous-classeSmartLightDevice
qui étend la super-classeSmartDevice
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
}
- Dans le corps de la sous-classe
SmartLightDevice
, définissez une propriétébrightnessLevel
affectée à une valeur0
avec une fonction setter qui spécifie une plage0..100
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
}
- Définissez une méthode
increaseBrightness()
qui augmente la luminosité de l'éclairage et imprime une chaîne"Brightness
increased
to
$brightnessLevel."
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
}
Relations entre les classes
Lorsque vous utilisez l'héritage, vous établissez une relation entre deux classes dans ce que l'on appelle une relation IS-A. Un objet est également une instance de la classe dont il hérite. Dans une relation HAS-A, un objet peut posséder une instance d'une autre classe sans être à proprement parler une instance de cette classe. Ces relations sont représentées dans le schéma ci-dessous :
Relations IS-A
Lorsque vous spécifiez une relation IS-A (littéralement EST-UN ou EST-UNE) entre la super-classe SmartDevice
et la sous-classe SmartTvDevice
, cela signifie que la super-classe SmartDevice
et la sous-classe SmartTvDevice
ont des capacités identiques. La relation est unidirectionnelle. Vous pouvez donc dire que chaque smart TV est un appareil connecté, mais vous ne pouvez pas dire que chaque appareil connecté est une smart TV. La représentation du code d'une relation IS-A est illustrée dans l'extrait de code ci-dessous :
// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}
N'utilisez pas l'héritage dans le seul but de rendre le code réutilisable. Avant de prendre une décision, vérifiez si les deux classes sont liées. S'il existe une relation entre les deux, vérifiez si elles sont vraiment éligibles à la relation IS-A. Demandez-vous si une sous-classe est aussi une super-classe. Par exemple, Android est un système d'exploitation.
Relations HAS-A
HAS-A (littéralement A-UN ou A-UNE) est une autre méthode utilisée pour spécifier la relation entre deux classes. Par exemple, il est probable que vous utilisiez la smart TV chez vous. Dans ce cas, il existe une relation entre la smart TV et votre domicile. La maison contient un appareil connecté ou, en d'autres termes, elle a un appareil connecté. La relation HAS-A entre deux classes est également appelée composition.
Jusqu'à présent, vous avez créé quelques appareils connectés. Vous allez maintenant créer la classe SmartHome
qui contient des appareils connectés. La classe SmartHome
vous permet d'interagir avec les appareils connectés.
Utilisez une relation HAS-A pour définir une classe SmartHome
:
- Définissez une classe
SmartHome
entre la classeSmartLightDevice
et la fonctionmain()
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
}
class SmartHome {
}
fun main() {
...
}
- Dans le constructeur de classe
SmartHome
, utilisez le mot cléval
pour créer une propriétésmartTvDevice
de typeSmartTvDevice
:
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {
}
- Dans le corps de la classe
SmartHome
, définissez une méthodeturnOnTv()
qui appelle la méthodeturnOn()
sur la propriétésmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
}
- Sur la ligne située après la méthode
turnOnTv()
, définissez une méthodeturnOffTv()
qui appelle la méthodeturnOff()
sur la propriétésmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
}
- Sur la ligne située après la méthode
turnOffTv()
, définissez une méthodeincreaseTvVolume()
qui appelle la méthodeincreaseSpeakerVolume()
sur la propriétésmartTvDevice
, puis définissez une méthodechangeTvChannelToNext()
qui appelle la méthodenextChannel()
sur la propriétésmartTvDevice
:
class SmartHome(val smartTvDevice: SmartTvDevice) {
fun turnOnTv() {
smartTvDevice.turnOn()
}
fun turnOffTv() {
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
}
- Dans le constructeur de la classe
SmartHome
, déplacez le paramètre de propriétésmartTvDevice
sur sa propre ligne, suivi d'une virgule :
class SmartHome(
val smartTvDevice: SmartTvDevice,
) {
...
}
- Sur la ligne qui suit la propriété
smartTvDevice
, utilisez le mot cléval
pour définir une propriétésmartLightDevice
de typeSmartLightDevice
:
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
}
- Dans la section
SmartHome
, définissez une méthodeturnOnLight()
qui appelle la méthodeturnOn()
sur l'objetsmartLightDevice
et une méthodeturnOffLight()
qui appelle la méthodeturnOff()
sur l'objetsmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
}
- Sur la ligne qui suit la méthode
turnOffLight()
, définissez une méthodeincreaseLightBrightness()
qui appelle la méhtodeincreaseBrightness()
sur la propriétésmartLightDevice
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
smartLightDevice.turnOn()
}
fun turnOffLight() {
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
}
- Sur la ligne qui suit la méthode
increaseLightBrightness()
, définissez une méthodeturnOffAllDevices()
qui appelle les mérthodesturnOffTv()
etturnOffLight()
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
...
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
Remplacer les méthodes de super-classe à partir de sous-classes
Comme indiqué précédemment, même si la fonctionnalité d'activation et de désactivation est prise en charge par tous les appareils connectés, la façon dont ils l'exécutent est différente. Pour fournir ce comportement spécifique à un appareil, vous devez remplacer les méthodes turnOn()
et turnOff()
définies dans la super-classe. Dans ce contexte, "remplacer" signifie intercepter l'action, généralement pour en prendre le contrôle manuel. Lorsque vous remplacez une méthode, la méthode de la sous-classe interrompt l'exécution de celle définie dans la super-classe et fournit sa propre exécution.
Remplacez les méthodes turnOn()
et turnOff()
de la classe SmartDevice
:
- Dans le corps de la super-classe
SmartDevice
, ajoutez un mot cléopen
avant le mot cléfun
de chaque méthode :
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
// function body
}
open fun turnOff() {
// function body
}
}
- Dans le corps de la classe
SmartLightDevice
, définissez une méthodeturnOn()
avec un corps vide :
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
}
}
- Dans le corps de la méthode
turnOn()
, définissez la propriétédeviceStatus
sur la chaîne "on
", définissez la propriétébrightnessLevel
sur une valeur de2
, ajoutez une instructionprintln()
, puis transmettez-lui une chaîne"$name
turned
on.
The
brightness
level
is
$brightnessLevel."
:
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
- Dans le corps de la classe
SmartLightDevice
, définissez une méthodeturnOff()
avec un corps vide :
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
fun turnOff() {
}
}
- Dans le corps de la méthode
turnOff()
, définissez la propriétédeviceStatus
sur la chaîne "off
", définissez la propriétébrightnessLevel
sur une valeur de0
, ajoutez une instructionprintln()
, puis transmettez-lui une chaîne"Smart
Light
turned
off"
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
fun turnOff() {
deviceStatus = "off"
brightnessLevel = 0
println("Smart Light turned off")
}
}
- Dans la sous-classe
SmartLightDevice
, avant le mot cléfun
des méthodesturnOn()
etturnOff()
, ajoutez le mot cléoverride
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
deviceStatus = "on"
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
deviceStatus = "off"
brightnessLevel = 0
println("Smart Light turned off")
}
}
Le mot clé override
indique à l'environnement d'exécution Kotlin d'exécuter le code inclus dans la méthode définie dans la sous-classe.
- Dans le corps de la classe
SmartTvDevice
, définissez une méthodeturnOn()
avec un corps vide :
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
fun turnOn() {
}
}
- Dans le corps de la méthode
turnOn()
, définissez la propriétédeviceStatus
sur la chaîne "on
", ajoutez une instructionprintln()
, puis transmettez-lui une chaîne"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber."
:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
deviceStatus = "on"
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
}
- Dans le corps de la classe
SmartTvDevice
, après la méthodeturnOn()
, définissez une méthodeturnOff()
avec un corps vide :
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
}
}
- Dans le corps de la méthode
turnOff()
, définissez la propriétédeviceStatus
sur la chaîne "off
", ajoutez une instructionprintln()
, puis transmettez-lui une chaîne"$name
turned
off"
:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {
...
fun turnOn() {
...
}
fun turnOff() {
deviceStatus = "off"
println("$name turned off")
}
}
- Dans la classe
SmartTvDevice
, avant le mot cléfun
des méthodesturnOn()
etturnOff()
, ajoutez le mot cléoverride
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
deviceStatus = "on"
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
deviceStatus = "off"
println("$name turned off")
}
}
- Dans la fonction
main()
, utilisez le mot clévar
pour définir une variablesmartDevice
de typeSmartDevice
qui instancie un objetSmartTvDevice
qui accepte un argument"Android
TV"
et un argument"Entertainment"
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
- Sur la ligne qui suit la variable
smartDevice
, appelez la méthodeturnOn()
sur l'objetsmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
}
- Exécutez le code.
La sortie est la suivante :
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
- Sur la ligne qui suit l'appel de la méthode
turnOn()
, réaffectez la variablesmartDevice
pour instancier une classeSmartLightDevice
qui accepte un argument"Google
Light"
et un argument"Utility"
. Appelez ensuite la méthodeturnOn()
sur la référence d'objetsmartDevice
:
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
- Exécutez le code.
La sortie est la suivante :
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1. Google Light turned on. The brightness level is 2.
Voici un exemple de polymorphisme. Le code appelle la méthode turnOn()
sur une variable de type SmartDevice
et, en fonction de la valeur réelle de la variable, différentes implémentations de la méthode turnOn()
peuvent être exécutées.
Réutiliser du code de super-classe dans des sous-classes avec le mot clé super
Si vous examinez attentivement les méthodes turnOn()
et turnOff()
, vous remarquerez une similitude dans la façon dont la variable deviceStatus
est mise à jour lorsque les méthodes sont appelées dans les sous-classes SmartTvDevice
et SmartLightDevice
: le code est dupliqué. Vous pouvez réutiliser le code lors de la mise à jour de l'état dans la classe SmartDevice
.
Pour appeler la méthode remplacée dans la super-classe à partir de la sous-classe, vous devez utiliser le mot clé super
. Appeler une méthode à partir de la super-classe revient à l'appeler depuis l'extérieur de la classe. Au lieu d'utiliser un opérateur .
entre l'objet et la méthode, vous devez utiliser le mot clé super
, qui indique au compilateur Kotlin d'appeler la méthode sur la super-classe plutôt que sur la sous-classe.
La syntaxe utilisée pour appeler la méthode à partir de la super-classe commence par un mot clé super
, suivi de l'opérateur .
, du nom de la fonction et d'une paire de parenthèses. Le cas échéant, les arguments sont placés entre parenthèses. La syntaxe est illustrée dans le schéma ci-dessous :
Réutilisez le code de la super-classe SmartDevice
:
- Supprimez les instructions
println()
des méthodesturnOn()
etturnOff()
, puis déplacez le code dupliqué des sous-classesSmartTvDevice
etSmartLightDevice
vers la super-classeSmartDevice
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
- Utilisez le mot clé
super
pour appeler les méthodes à partir de la classeSmartDevice
dans les sous-classesSmartTvDevice
etSmartLightDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
super.turnOn()
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
super.turnOff()
println("$name turned off")
}
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("Smart Light turned off")
}
}
Remplacer les propriétés de super-classe à partir de sous-classes
Vous pouvez remplacer les propriétés en suivant la même procédure que celle utilisée pour les méthodes.
Remplacez la propriété deviceType
:
- Dans la super-classe
SmartDevice
, sur la ligne qui suit la propriétédeviceStatus
, utilisez les mots clésopen
etval
pour définir une propriétédeviceType
définie sur une chaîne"unknown"
:
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
open val deviceType = "unknown"
...
}
- Dans la classe
SmartTvDevice
, utilisez les mots clésoverride
etval
pour définir une propriétédeviceType
définie sur une chaîne"Smart
TV"
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
...
}
- Dans la classe
SmartLightDevice
, utilisez les mots clésoverride
etval
pour définir une propriétédeviceType
définie sur une chaîne"Smart
Light"
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
...
}
8. Modificateurs de visibilité
Les modificateurs de visibilité jouent un rôle important pour l'obtention de l'encapsulation :
- Dans une classe, ils vous permettent de masquer vos propriétés et méthodes afin d'empêcher tout accès non autorisé depuis l'extérieur.
- Dans un package, ils vous permettent de masquer les classes et interfaces afin d'empêcher tout accès non autorisé depuis l'extérieur.
Kotlin propose quatre modificateurs de visibilité :
public
. Modificateur de visibilité par défaut. Rend la déclaration accessible depuis n'importe où. Les propriétés et méthodes que vous souhaitez rendre accessibles à l'extérieur de la classe sont marquées comme publiques.private
. Rend la déclaration accessible dans le même fichier de classe ou le même fichier source.
Il existe probablement des propriétés et des méthodes qui ne sont utilisées qu'à l'intérieur de la classe et que vous ne souhaitez pas forcément rendre accessibles à d'autres classes. Ces propriétés et méthodes peuvent être marquées à l'aide du modificateur de visibilité private
pour empêcher qu'une autre classe n'y accède accidentellement.
protected
. Rend la déclaration accessible dans des sous-classes. Les propriétés et méthodes que vous souhaitez rendre accessibles dans la classe qui les définit et dans les sous-classes sont marquées à l'aide du modificateur de visibilitéprotected
.internal
. Rend la déclaration accessible dans le même module. Le modificateur "internal" est semblable au modificateur "private", à la différence que vous pouvez accéder aux propriétés et méthodes internes depuis l'extérieur de la classe, à condition que l'accès s'effectue dans le même module.
Lorsque vous définissez une classe, elle est visible publiquement et tout package qui l'importe peut y accéder. Cela signifie qu'elle est publique par défaut, sauf si vous spécifiez un modificateur de visibilité. De même, lorsque vous définissez ou déclarez des propriétés et des méthodes dans la classe, elles sont accessibles par défaut en dehors de la classe via l'objet de classe. Il est essentiel de définir une visibilité appropriée pour le code, principalement pour masquer les propriétés et les méthodes auxquelles les autres classes ne doivent pas accéder.
Voyons, par exemple, de quelle façon une voiture est rendue accessible à un conducteur. Les spécificités des pièces qui composent la voiture et son fonctionnement interne sont masqués par défaut. La voiture est conçue pour être utilisée de la façon la plus intuitive possible. Vous ne voudriez pas que votre voiture soit aussi difficile à utiliser qu'un avion de ligne, tout comme vous ne voudriez pas qu'un autre développeur ou que votre futur "vous" ayez des doutes quant aux propriétés et méthodes d'une classe qui sont censées être utilisées.
Les modificateurs de visibilité vous aident à présenter les parties pertinentes du code à d'autres classes de votre projet et permettent d'éviter l'utilisation accidentelle de l'implémentation, ce qui rend le code plus facile à comprendre et moins sujet aux bugs.
Le modificateur de visibilité doit être placé avant la syntaxe de déclaration, tout en déclarant la classe, la méthode ou les propriétés, comme illustré dans le schéma ci-dessous :
Spécifier un modificateur de visibilité pour les propriétés
La syntaxe permettant de spécifier le modificateur de visibilité d'une propriété commence par le modificateur private
, protected
ou internal
, suivi de la syntaxe qui définit une propriété. La syntaxe est illustrée dans le schéma ci-dessous :
L'extrait de code ci-dessous, par exemple, vous montre comment rendre la propriété deviceStatus
privée :
open class SmartDevice(val name: String, val category: String) {
...
private var deviceStatus = "online"
...
}
Vous pouvez également définir les modificateurs de visibilité sur des fonctions setter. Le modificateur est placé avant le mot clé set
. La syntaxe est illustrée dans le schéma ci-dessous :
Pour la classe SmartDevice
, la valeur de la propriété deviceStatus
doit être lisible en dehors de la classe via des objets de classe. Toutefois, seuls la classe et ses enfants doivent être en mesure de mettre à jour ou d'écrire la valeur. Pour implémenter cette exigence, vous devez utiliser le modificateur protected
sur la fonction set()
de la propriété deviceStatus
.
Utilisez le modificateur protected
sur la fonction set()
de la propriété deviceStatus
:
- Dans la propriété
deviceStatus
de la super-classeSmartDevice
, ajoutez le modificateurprotected
à la fonctionset()
:
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set(value) {
field = value
}
...
}
Vous n'effectuez aucune action ni vérification dans la fonction set()
. Il vous suffit d'affecter le paramètre value
à la variable field
. Comme nous l'avons vu précédemment, cette méthode est semblable à l'implémentation par défaut des setters de propriété. Vous pouvez omettre les parenthèses et le corps de la fonction set()
dans ce cas :
open class SmartDevice(val name: String, val category: String) {
...
var deviceStatus = "online"
protected set
...
}
- Dans la classe
SmartHome
, définissez une propriétédeviceTurnOnCount
sur une valeur0
avec une fonction setter privée :
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
...
}
- Ajoutez la propriété
deviceTurnOnCount
suivie de l'opérateur arithmétique++
aux méthodesturnOnTv()
etturnOnLight()
, puis ajoutez la propriétédeviceTurnOnCount
suivie de l'opérateur arithmétique--
àturnOffTv()
etturnOffLight()
:
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
fun turnOnTv() {
deviceTurnOnCount++
smartTvDevice.turnOn()
}
fun turnOffTv() {
deviceTurnOnCount--
smartTvDevice.turnOff()
}
...
fun turnOnLight() {
deviceTurnOnCount++
smartLightDevice.turnOn()
}
fun turnOffLight() {
deviceTurnOnCount--
smartLightDevice.turnOff()
}
...
}
Modificateurs de visibilité pour les méthodes
La syntaxe permettant de spécifier un modificateur de visibilité pour une méthode commence par le modificateur private
, protected
ou internal
, suivi de la syntaxe qui définit une méthode. La syntaxe est illustrée dans le schéma ci-dessous :
Vous pouvez consulter l'extrait de code suivant pour savoir comment spécifier un modificateur protected
pour la méthode nextChannel()
dans la classe SmartTvDevice
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
protected fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
...
}
Modificateurs de visibilité pour les constructeurs
La syntaxe permettant de spécifier un modificateur de visibilité pour un constructeur est semblable à la définition du constructeur principal, à quelques différences près :
- Le modificateur est spécifié après le nom de la classe, mais avant le mot clé
constructor
. - Si vous devez spécifier le modificateur pour le constructeur principal, vous devez conserver le mot clé et les parenthèses
constructor
, même en l'absence de paramètres.
La syntaxe est illustrée dans le schéma ci-dessous :
Vous pouvez consulter l'extrait de code suivant pour savoir comment ajouter un modificateur protected
au constructeur SmartDevice
:
open class SmartDevice protected constructor (val name: String, val category: String) {
...
}
Modificateurs de visibilité pour les classes
La syntaxe permettant de spécifier un modificateur de visibilité pour une classe commence par le modificateur private
, protected
ou internal
, suivi de la syntaxe qui définit une classe. La syntaxe est illustrée dans le schéma ci-dessous :
Vous pouvez consulter l'extrait de code suivant pour savoir comment spécifier un modificateur internal
pour la classe SmartDevice
:
internal open class SmartDevice(val name: String, val category: String) {
...
}
Idéalement, vous devez vous efforcer d'assurer une visibilité stricte des propriétés et des méthodes. Pour cela, vous devez les déclarer le plus souvent possible avec le modificateur private
. Si vous ne pouvez pas maintenir leur caractère privé, utilisez le modificateur protected
. Si vous ne pouvez pas assurer leur protection, utilisez le modificateur internal
. Si vous ne pouvez pas maintenir leur caractère interne, utilisez le modificateur public
.
Spécifier les modificateurs de visibilité appropriés
Ce tableau vous aide à déterminer les modificateurs de visibilité appropriés en fonction de l'endroit où la propriété ou les méthodes d'une classe ou d'un constructeur doivent être accessibles :
Modificateur | Accessible dans la même classe | Accessible dans la sous-classe | Accessible dans le même module | Accessible en dehors du module |
| ✔ | 𝗫 | 𝗫 | 𝗫 |
| ✔ | ✔ | 𝗫 | 𝗫 |
| ✔ | ✔ | ✔ | 𝗫 |
| ✔ | ✔ | ✔ | ✔ |
Dans la sous-classe SmartTvDevice
, vous ne devez pas autoriser le contrôle des propriétés speakerVolume
et channelNumber
depuis l'extérieur de la classe. Ces propriétés doivent uniquement être contrôlées via les méthodes increaseSpeakerVolume()
et nextChannel()
.
De même, dans la sous-classe SmartLightDevice
, la propriété brightnessLevel
ne doit être contrôlée que via la méthode increaseLightBrightness()
.
Ajoutez les modificateurs de visibilité appropriés aux sous-classes SmartTvDevice
et SmartLightDevice
:
- Dans la classe
SmartTvDevice
, ajoutez un modificateur de visibilitéprivate
aux propriétésspeakerVolume
etchannelNumber
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
private var speakerVolume = 2
set(value) {
if (value in 0..100) {
field = value
}
}
private var channelNumber = 1
set(value) {
if (value in 0..200) {
field = value
}
}
...
}
- Dans la classe
SmartLightDevice
, ajoutez un modificateurprivate
à la propriétébrightnessLevel
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
...
private var brightnessLevel = 0
set(value) {
if (value in 0..100) {
field = value
}
}
...
}
9. Définir des délégués de propriété
Dans la section précédente, nous avons vu que les propriétés en langage Kotlin utilisaient un champ de stockage pour stocker leurs valeurs en mémoire. Utilisez l'identifiant field
pour le référencer.
Si vous examinez le code écrit jusqu'à présent, vous pouvez voir le code dupliqué qui est utilisé pour vérifier si les valeurs se trouvent dans la plage définie pour les propriétés speakerVolume
, channelNumber
et brightnessLevel
dans les classes SmartTvDevice
et SmartLightDevice
. Vous pouvez réutiliser le code de contrôle de plage dans la fonction setter avec des délégués. Le délégué se charge de la gestion de la valeur et vous ne devez donc pas utiliser de champ ni les fonctions getter et setter pour la gérer.
La syntaxe permettant de créer des délégués de propriété commence par la déclaration d'une variable, suivie du mot clé by
, puis de l'objet délégué qui gère les fonctions getter et setter de la propriété. La syntaxe est illustrée dans le schéma ci-dessous :
Avant d'implémenter la classe à laquelle vous pouvez déléguer l'implémentation, vous devez vous familiariser avec les interfaces. Une interface est un contrat auquel les classes qui l'implémentent doivent se conformer. Elle met l'accent sur ce qu'il faut faire plutôt que sur la façon de le faire. En bref, une interface vous aide à réaliser l'abstraction.
Par exemple, avant de construire une maison, vous exposez vos desiderata à l'architecte. Vous voulez une chambre parentale, une chambre d'enfant, une salle de séjour, une cuisine et deux salles de bain. En bref, vous spécifiez ce que vous souhaitez et l'architecte spécifie comment arriver à ce résultat. La syntaxe permettant de créer une interface est illustrée dans le schéma ci-dessous :
Nous avons vu précédemment comment étendre une classe et comment remplacer ses fonctionnalités. Avec les interfaces, la classe implémente l'interface. La classe fournit les détails d'implémentation nécessaires pour les méthodes et propriétés déclarées dans l'interface. Vous procéderez de la même façon avec l'interface ReadWriteProperty
pour créer le délégué. Les interfaces seront traitées plus en détail dans le module suivant.
Pour créer la classe déléguée pour le type var
, vous devez implémenter l'interface ReadWriteProperty
. De même, vous devez implémenter l'interface ReadOnlyProperty
pour le type val
.
Créez le délégué pour le type var
:
- Avant la fonction
main()
, créez une classeRangeRegulator
qui implémente l'interfaceInt>
ReadWriteProperty<Any?,
:
class RangeRegulator() : ReadWriteProperty<Any?, Int> {
}
fun main() {
...
}
Ne vous souciez pas des chevrons ni de ce qu'ils contiennent. Ils représentent des types génériques. Nous en reparlerons dans le module suivant.
- Dans le constructeur principal de la classe
RangeRegulator
, ajoutez un paramètreinitialValue
, une propriétéminValue
privée et une propriétémaxValue
privée, tous de typeInt
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
}
- Dans le corps de la classe
RangeRegulator
, remplacez les méthodesgetValue()
etsetValue()
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
Ces méthodes agissent comme fonctions getter et setter des propriétés.
- Sur la ligne qui précède la classe
SmartDevice
, importez les interfacesReadWriteProperty
etKProperty
:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class SmartDevice(val name: String, val category: String) {
...
}
...
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
...
- Dans la classe
RangeRegulator
, sur la ligne qui précède la méthodegetValue()
, définissez une propriétéfieldData
et initialisez-la avec le paramètreinitialValue
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
Cette propriété sert de champ de stockage pour la variable.
- Dans le corps de la méthode
getValue()
, renvoyez la propriétéfieldData
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
}
}
- Dans le corps de la méthode
setValue()
, vérifiez si le paramètrevalue
en cours d'affectation se trouve dans la plageminValue..maxValue
avant de l'affecter à la propriétéfieldData
:
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
- Dans la classe
SmartTvDevice
, utilisez la classe déléguée pour définir les propriétésspeakerVolume
etchannelNumber
:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)
...
}
- Dans la classe
SmartLightDevice
, utilisez la classe déléguée pour définir la propriétébrightnessLevel
:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)
...
}
10. Tester la solution
Le code de la solution est présenté dans l'extrait de code suivant :
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
protected set
open val deviceType = "unknown"
open fun turnOn() {
deviceStatus = "on"
}
open fun turnOff() {
deviceStatus = "off"
}
}
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)
fun increaseSpeakerVolume() {
speakerVolume++
println("Speaker volume increased to $speakerVolume.")
}
fun nextChannel() {
channelNumber++
println("Channel number increased to $channelNumber.")
}
override fun turnOn() {
super.turnOn()
println(
"$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
"set to $channelNumber."
)
}
override fun turnOff() {
super.turnOff()
println("$name turned off")
}
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)
fun increaseBrightness() {
brightnessLevel++
println("Brightness increased to $brightnessLevel.")
}
override fun turnOn() {
super.turnOn()
brightnessLevel = 2
println("$name turned on. The brightness level is $brightnessLevel.")
}
override fun turnOff() {
super.turnOff()
brightnessLevel = 0
println("Smart Light turned off")
}
}
class SmartHome(
val smartTvDevice: SmartTvDevice,
val smartLightDevice: SmartLightDevice
) {
var deviceTurnOnCount = 0
private set
fun turnOnTv() {
deviceTurnOnCount++
smartTvDevice.turnOn()
}
fun turnOffTv() {
deviceTurnOnCount--
smartTvDevice.turnOff()
}
fun increaseTvVolume() {
smartTvDevice.increaseSpeakerVolume()
}
fun changeTvChannelToNext() {
smartTvDevice.nextChannel()
}
fun turnOnLight() {
deviceTurnOnCount++
smartLightDevice.turnOn()
}
fun turnOffLight() {
deviceTurnOnCount--
smartLightDevice.turnOff()
}
fun increaseLightBrightness() {
smartLightDevice.increaseBrightness()
}
fun turnOffAllDevices() {
turnOffTv()
turnOffLight()
}
}
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
fun main() {
var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
smartDevice.turnOn()
smartDevice = SmartLightDevice("Google Light", "Utility")
smartDevice.turnOn()
}
La sortie est la suivante :
Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1. Google Light turned on. The brightness level is 2.
11. Relevez ce défi !
- Dans la classe
SmartDevice
, définissez une méthodeprintDeviceInfo()
qui imprime une chaîne"Device
name:
$name,
category:
$category,
type:
$deviceType"
. - Dans la classe
SmartTvDevice
, définissez une méthodedecreaseVolume()
qui diminue le volume et une méthodepreviousChannel()
qui accède à la chaîne précédente. - Dans la classe
SmartLightDevice
, définissez une méthodedecreaseBrightness()
qui diminue la luminosité. - Dans la classe
SmartHome
, assurez-vous que toutes les actions ne peuvent être effectuées que lorsque la propriétédeviceStatus
de chaque appareil est définie sur une chaîne"on"
. Assurez-vous également que la propriétédeviceTurnOnCount
est mise à jour correctement.
Une fois l'implémentation terminée :
- Dans la classe
SmartHome
, définissez les méthodesdecreaseTvVolume()
,changeTvChannelToPrevious()
,printSmartTvInfo()
,printSmartLightInfo()
etdecreaseLightBrightness()
. - Appelez les méthodes appropriées à partir des classes
SmartTvDevice
etSmartLightDevice
dans la classeSmartHome
. - Dans la fonction
main()
, appelez les méthodes qui ont été ajoutées pour les tester.
12. Conclusion
Félicitations ! Vous avez appris à définir des classes et à instancier des objets. Vous avez également appris à créer des relations entre les classes et à créer des délégués de propriété.
Résumé
- La programmation orientée objet repose sur quatre grands principes : l'encapsulation, l'abstraction, l'héritage et le polymorphisme.
- Les classes sont définies avec le mot clé
class
, et contiennent des propriétés et des méthodes. - Les propriétés sont semblables aux variables, si ce n'est qu'elles peuvent avoir des getters et des setters personnalisés.
- Un constructeur indique comment instancier les objets d'une classe.
- Vous pouvez omettre le mot clé
constructor
lorsque vous définissez un constructeur principal. - L'héritage facilite la réutilisation du code.
- La relation IS-A fait référence à l'héritage.
- La relation HAS-A fait référence à la composition.
- Les modificateurs de visibilité jouent un rôle important pour l'obtention de l'encapsulation.
- Kotlin propose quatre modificateurs de visibilité :
public
,private
,protected
etinternal
. - Un délégué de propriété vous permet de réutiliser le code getter et setter dans plusieurs classes.