La programmation orientée objet dans le langage de programmation Go

Ce chapitre vous explique la programmation orientée objet en GoLang. Même si GO n'est pas un langage purement orienté objet il est possible en réussissant à combiner les structures et les packages de se rapprocher le plus possible de la POO dans le langage de programmation GO.

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

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Fais attention à bien encapsuler la logique du plateau dans une structure dédiée. Ça t'évitera de finir avec un main.go de 500 lignes.

05/04/2019 à 15:46
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

@tina-arouna merci !

05/04/2019 à 12:32
francoise06
Membre Actif
Avatar de francoise06
francoise06
Membre Actif

Merci pour le tuto, je vais essayer de coder le morpion comme suggéré.

05/04/2019 à 08:53
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie ton go.mod. Si tu es en modules, le nom du module doit correspondre au chemin d'import.

05/04/2019 à 03:29
anne-gros
Membre Actif
Avatar de anne-gros
anne-gros
Membre Actif

J'ai une erreur undefined: personnage alors que mon fichier est bien dans le dossier. Des pistes ?

04/04/2019 à 20:56
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est le prix à payer pour ne pas avoir de hiérarchie de classes lourdingue. C'est plus sain à long terme.

04/04/2019 à 15:14
matthieu-briand
Membre Actif
Avatar de matthieu-briand
matthieu-briand
Membre Actif

Le coup des packages pour simuler les classes, c'est un peu verbeux non ?

04/04/2019 à 11:04
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Utilise des slices []string si tu veux être flexible. Le tableau fixe c'était juste pour l'exemple.

04/04/2019 à 06:49
gabriel-guyon
Membre Actif
Avatar de gabriel-guyon
gabriel-guyon
Membre Actif

Pourquoi utiliser [3]string pour l'inventaire ? C'est super rigide.

03/04/2019 à 23:44
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Non, pas de surcharge. Tu nommes tes fonctions différemment, genre NewPersonnage et NewPersonnageVide.

03/04/2019 à 18:41
llanglois
Membre Actif
Avatar de llanglois
llanglois
Membre Actif

Dites, pour le constructeur, est-ce qu'on peut avoir plusieurs New ? Go gère pas la surcharge.

03/04/2019 à 12:29
vaillant-olivier
Membre Actif
Avatar de vaillant-olivier
vaillant-olivier
Membre Actif

Super clair, j'ai réussi à implémenter mon propre constructeur.

03/04/2019 à 06:57
tina-arouna
Membre
Avatar de tina-arouna
tina-arouna
Membre

j'ai adoré

03/04/2019 à 02:46
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Si tu veux modifier l'objet, retourne un pointeur :

func New(...) *Personnage {
    return &Personnage{...}
}
03/04/2019 à 00:15
aimee89
Membre Actif
Avatar de aimee89
aimee89
Membre Actif

J'ai essayé de passer un pointeur à la méthode New mais ça plante. Une idée ?

02/04/2019 à 18:44
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, utilise une minuscule. Si tu renommes Vie en vie dans ta struct, elle sera privée au package. À ne jamais oublier : seule la majuscule exporte.

02/04/2019 à 13:36
neveu-julie
Membre Actif
Avatar de neveu-julie
neveu-julie
Membre Actif

Est-ce qu'on peut rendre les attributs privés ? Je veux pas qu'on modifie la vie du perso depuis le main.go.

02/04/2019 à 06:06
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Bien vu, c'est une coquille dans mon exemple. Faut corriger par p.Nom ou autre chose, mais p.Vie c'est effectivement une erreur de copier-coller.

02/04/2019 à 01:39
ublot
Membre Actif
Avatar de ublot
ublot
Membre Actif

L'exemple Affichage() dans le fichier personnage.go est sympa. Par contre, j'ai une erreur sur p.Vie dans le fmt.Println final, tu affiches la vie au lieu de l'inventaire ?

01/04/2019 à 18:41
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

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.

01/04/2019 à 11:52
lebon-victor
Membre Actif
Avatar de lebon-victor
lebon-victor
Membre Actif

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 ?

01/04/2019 à 05:45
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

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 :

$GOPATH/src
├── personnage
| ├── personnage.go
main.go
31/03/2019 à 22:17
alexandrie60
Membre Actif
Avatar de alexandrie60
alexandrie60
Membre Actif

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 package personnage.

31/03/2019 à 17:36

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire