Go est-il un langage orienté objet ?
Promesse faite, promesse tenue, je vais vous parler de la POO (programmation orientée objet) en Go.
Voici d'abord ce qu'on peut retrouver dans la faq de go à propos de la POO.
Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).
En gros il nous explique qu'il n'existe pas de hiérarchie des types en Go (par exemple la notion d'héritage). Mais qu'il existe des approches différentes de hiérarchisation de nos classes en utilisant par exemple les packages imbriqués où le répertoire racine sera la classe mère et les sous dossier des classes filles qui qui héritent de la classe mere.
Pour résumé Go n'est pas un langage de programmation purement orienté objet . Mais en combinant les structures et les packages, il est réalisable de se rapprocher le plus possible de la POO.
Création de nos classes
On va reprendre le même exemple vu dans le chapitre des structures, à savoir une structure Personnage avec les attributs suivants :
- la vie du personnage
- la puissance du personnage
- le nom du personnage
- Savoir si le personnage est mort ou pas
- L'inventaire du personnage
Et avec les méthodes suivantes :
- Presentation()
- ViePerdu()
- EstMort()
Nous allons créer notre structure Personnage dans un package personnage, ce qui nous donnera l'arborescence suivante :
$GOPATH/src
├── personnage
| ├── personnage.go
main.go
Voici à quoi va ressembler notre classe Personnage dans le fichier personnage.go :
package personnage
import (
"fmt"
)
type Personnage struct {
Nom string
Vie int
Puissance int
Mort bool
Inventaire [3]string
}
/*
Affiche des informations sur un personnage
@return: void
*/
func (p Personnage) Affichage() { // déclaration de ma méthode Affichage() liée à ma structure Personnage
fmt.Println("--------------------------------------------------")
fmt.Println("Vie du personnage", p.Nom, ":", p.Vie)
fmt.Println("Puissance du personnage", p.Nom, ":", p.Puissance)
if p.Mort {
fmt.Println("Vie du personnage", p.Nom, "est mort")
} else {
fmt.Println("Vie du personnage", p.Nom, "est vivant")
}
fmt.Println("\nLe personnage", p.Nom, "possède dans son inventaire :", p.Vie)
for _, item := range p.Inventaire {
fmt.Println("-", item)
}
}
Rappel
Go exporte une variable/fonctions dans un autre package si et seulement si le nom commence par une lettre majuscule. Toutes les autres attributs/fonctions ne commençant pas par une lettre majuscule elles sont considérées comme variables privées ou fonctions privées par le paquet, c'est-à-dire qu'elles ne sont pas exploitables en dehors du package où elles ont été initialisées.
Ensuite voici à quoi va ressembler notre fichier main.go
package main
import (
"personnage"
)
func main() {
magicien := personnage.Personnage{ // Instanciation de la classe Personnage
Nom: "magix",
Vie: 100,
Puissance: 20,
Mort: false,
Inventaire: [3]string{"potions", "poisons", "bâton"},
}
magicien.Affichage()
}
Ici on importe le package personnage et sa structure Personnage qui pour nous représente une classe.
Une fois notre package importé, on crée ensuite une instance de notre classe qu'on nomme magicien et on lui affecte des valeurs par défaut.
À la fin de notre programme on invoque la méthode de notre classe Personnage, ce qui nous donne comme résultat :
--------------------------------------------------
Vie du personnage magix : 100
Puissance du personnage magix : 20
Vie du personnage magix est vivant
Le personnage magix possède dans son inventaire : 100
- potions
- poisons
- bâton
Les constructeurs
Le rôle du constructeur est de déclarer et d'initialiser les attributs d'une classe. Go ne supporte pas par défaut les constructeurs, mais il est possible de les créer en bidouillant avec les méthodes.
Avant de vous montrer comment on créée une sorte de constructeur en Go, voici d'abord une comparaison avec le le langage Java.
Voilà à quoi va ressembler par exemple notre structure Personnage en java:
public class Personnage{
public String Nom;
public int Vie;
public int Puissance;
public boolean Mort;
String tableauChaine[] = {"rien", "rien", "rien"};
}
Voici comment on instancie une classe en java :
public class Main
{
public static void main(String[] args)
{
// Instanciation de la classe Personnage
Personnage magicien = new Personnage("magix", 100, 20, false, new String[] {"potions", "poisons", "bâton"} );
}
}
C'est grâce à l'opérateur new, qu'on arrive à faire appel à un constructeur d'une classe en java. Sur Go on va bidouiller un peu dans notre package personnage pour recréer l'opérateur new utilisé dans java (mais aussi dans beaucoup d'autres langages de programmations).
Pour ça on va créer une méthode nommée New dans package personnage, comme suit :
package personnage
import (
"fmt"
)
type Personnage struct {
Nom string
Vie int
Puissance int
Mort bool
Inventaire [3]string
}
/*
Créer une instance de la classe Personnage
@return: struct Personnage
*/
func New(Nom string, Vie int, Puissance int, Mort bool, Inventaire [3]string) Personnage {
personnage := Personnage{Nom, Vie, Puissance, Mort, Inventaire}
return personnage
}
/*
Affiche des informations sur un personnage
@return: void
*/
func (p Personnage) Affichage() { // déclaration de ma méthode Affichage() liée à ma structure Personnage
fmt.Println("--------------------------------------------------")
fmt.Println("Vie du personnage", p.Nom, ":", p.Vie)
fmt.Println("Puissance du personnage", p.Nom, ":", p.Puissance)
if p.Mort {
fmt.Println("Vie du personnage", p.Nom, "est mort")
} else {
fmt.Println("Vie du personnage", p.Nom, "est vivant")
}
fmt.Println("\nLe personnage", p.Nom, "possède dans son inventaire :", p.Vie)
for _, item := range p.Inventaire {
fmt.Println("-", item)
}
}
Notre fichier main.go va ressembler à ça :
package main
import (
"personnage"
)
func main() {
magicien := personnage.New("magix", 100, 20, false, [3]string{"potions", "poisons", "bâton"}) //Instanciation de la classe Personnage
magicien.Affichage()
}
Conclusion
Vous l'aurez sans doute compris même si Go ne prend pas en charge les classes, il reste tout de même possible d'utiliser les structures à la place des classes. Grâce à cette fusion entre structures et packages il est possible d'encore mieux organiser son code en s'orientant plus vers la POO. Mon but sur ce chapitre était de vous introduire à la POO. Il est bien sûr tout à fait possible d'aller encore plus loin en créant par exemple une notion d'héritage grâce aux interfaces, je créerai sûrement un article à propos de ce sujet.
Suggestion d'exercice
Avant de nous quitter sur ce chapitre je vous conseil de recréer le TP sur les morpions avec des classes ! Vous avez largement les ressources nécessaires pour réussir à bien cet exercice.
Espace commentaire
Écrire un commentaire
Rejoignez la discussion
Vous devez être connecté pour poster un message.
23 commentaires
Fais attention à bien encapsuler la logique du plateau dans une structure dédiée. Ça t'évitera de finir avec un
main.gode 500 lignes.@tina-arouna merci !
Merci pour le tuto, je vais essayer de coder le morpion comme suggéré.
Vérifie ton
go.mod. Si tu es en modules, le nom du module doit correspondre au chemin d'import.J'ai une erreur
undefined: personnagealors que mon fichier est bien dans le dossier. Des pistes ?C'est le prix à payer pour ne pas avoir de hiérarchie de classes lourdingue. C'est plus sain à long terme.
Le coup des packages pour simuler les classes, c'est un peu verbeux non ?
Utilise des slices
[]stringsi tu veux être flexible. Le tableau fixe c'était juste pour l'exemple.Pourquoi utiliser
[3]stringpour l'inventaire ? C'est super rigide.Non, pas de surcharge. Tu nommes tes fonctions différemment, genre
NewPersonnageetNewPersonnageVide.Dites, pour le constructeur, est-ce qu'on peut avoir plusieurs
New? Go gère pas la surcharge.Super clair, j'ai réussi à implémenter mon propre constructeur.
j'ai adoré
Si tu veux modifier l'objet, retourne un pointeur :
J'ai essayé de passer un pointeur à la méthode
Newmais ça plante. Une idée ?Oui, utilise une minuscule. Si tu renommes
Vieenviedans ta struct, elle sera privée au package. À ne jamais oublier : seule la majuscule exporte.Est-ce qu'on peut rendre les attributs privés ? Je veux pas qu'on modifie la vie du perso depuis le
main.go.Bien vu, c'est une coquille dans mon exemple. Faut corriger par
p.Nomou autre chose, maisp.Viec'est effectivement une erreur de copier-coller.L'exemple
Affichage()dans le fichierpersonnage.goest sympa. Par contre, j'ai une erreur surp.Viedans lefmt.Printlnfinal, tu affiches la vie au lieu de l'inventaire ?C'est la méthode préconisée. On appelle ça la composition. Tu imbriques tes types, mais évite de trop complexifier la hiérarchie sinon c'est l'enfer à maintenir.
Pas mal le tuto. Question bête : vu que Go ne gère pas l'héritage, est-ce que c'est propre d'imbriquer des structures pour simuler ça ?
T'as bien vérifié ton
$GOPATH? Si ton dossier est en dehors, il ne le verra jamais. Faut que la structure respecte l'arborescence :J'ai testé ton approche pour le constructeur, mais j'ai une erreur de compilation avec
go run main.go. Il ne trouve pas le packagepersonnage.