Maison > développement back-end > Golang > Introduction à la programmation orientée objet (POO) en Golang

Introduction à la programmation orientée objet (POO) en Golang

Linda Hamilton
Libérer: 2024-12-23 12:43:11
original
384 Les gens l'ont consulté

Quand nous parlons de programmation, nous entendons généralement écrire un tas de fonctions qui modifient et interagissent avec certaines données. La programmation orientée objet (POO) est un modèle de programmation qui se concentre plutôt sur des « objets » qui contiennent des données et auxquels sont attachées certaines fonctions pertinentes. La programmation orientée objet repose sur quatre piliers : l'héritage, l'encapsulation, le polymorphisme et l'abstraction. Dans ce blog, nous verrons comment vous pouvez implémenter chacun d'eux dans Golang avec des exemples. Une idée de base sur la POO est recommandée, mais sinon, je donnerai une brève introduction à la signification des quatre piliers.

Introduction to Object Oriented Programming (OOP) in Golang

Classes, objets et méthodes

L'idée centrale de la programmation orientée objet peut être résumée dans ces puces :

  • Vous définissez des « classes » qui sont un ensemble de données et de fonctions que vous pouvez appeler sur ces données.
  • Ces fonctions spécifiques sont appelées « méthodes » de cette classe particulière.
  • Une instance réelle d'une classe est appelée un « objet ».

Regardons du code en Golang pour comprendre ces trois concepts :

package main

import "fmt"

type Batman struct {
    actor string
    year int
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year)
}

func main() {
    b1 := Batman{actor: "Michael Keaton", year: 1989}
    b2 := Batman{actor: "Christian Bale", year: 2005}

    b1.SayImBatman()
    b2.SayImBatman()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans Golang, les classes ne sont que des types définis par nos soins. Ces types ne doivent pas nécessairement être une structure, mais ils le sont généralement car en POO, nous travaillons avec une collection de données, qui peuvent être de n'importe quel type (chaîne, int, etc.).

Les classes sont des plans d'objets. Chaque fois que vous instanciez une classe, un objet est formé. Dans cet exemple, b1 et b2 sont des objets de la classe Batman.

La fonction SayImBatman peut être appelée sur n'importe quel objet de la classe. Puisqu'elle est liée à la classe Batman, au lieu de l'appeler une fonction normale, on l'appelle une méthode de la classe.

Je pense que cela devrait clarifier suffisamment les bases de la POO pour que vous puissiez passer à la section suivante, où nous examinons les quatre piliers de la POO.

Héritage

L'héritage introduit les concepts de classes parent et enfant en POO. Une classe enfant est une classe dérivée d'une classe parent et hérite de toutes ses méthodes et propriétés (données). Regardons un code qui nous aidera à comprendre ceci :

package main

import "fmt"

type Hero struct {
    team string
}

type Batman struct {
    Hero
    name string
}

type Ironman struct {
    Hero
    power int
}

func (h Hero) SayTeam() {
    fmt.Println("My Team is", h.team)
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman\n", b.name)
}

func (i Ironman) SayPowerLevel() {
    fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power)
}

func main() {
    b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"}
    i1 := Ironman{Hero{team: "Avengers"}, 23}

    b1.SayImBatman()
    b1.SayTeam()

    i1.SayPowerLevel()
    i1.SayTeam()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans cet exemple, Batman et Ironman sont des classes enfants de la classe parent Hero. Ils ont accès aux propriétés de leur classe parent, c’est-à-dire team, et à ses méthodes, c’est-à-dire SayTeam. Comme vous pouvez le voir lors de la déclaration des instances b1 et i1, nous spécifions les propriétés de la classe parent ainsi que leurs propriétés spécifiques pour les classes respectives. Les deux sont capables d’appeler la méthode SayTeam définie sur la classe parent. Mais ils ont aussi des propriétés et des méthodes distinctes qui sont propres à chacun d'eux.

Golang implémente l'héritage en utilisant la composition (en utilisant une structure à l'intérieur d'une structure). Il n'a pas d'héritage intégré basé sur les classes comme les autres langages POO tels que C ou Java.

Encapsulation

L'encapsulation est le principe qui consiste à masquer les propriétés internes d'un objet et à ne pas permettre leur modification directe. Au lieu de cela, il repose sur la fourniture de méthodes pour obtenir et mettre à jour ces propriétés. Regardons un exemple pour mieux comprendre cela :

package main

import "fmt"

type Batman struct {
    actor string
    year int
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year)
}

func main() {
    b1 := Batman{actor: "Michael Keaton", year: 1989}
    b2 := Batman{actor: "Christian Bale", year: 2005}

    b1.SayImBatman()
    b2.SayImBatman()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
package main

import "fmt"

type Hero struct {
    team string
}

type Batman struct {
    Hero
    name string
}

type Ironman struct {
    Hero
    power int
}

func (h Hero) SayTeam() {
    fmt.Println("My Team is", h.team)
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman\n", b.name)
}

func (i Ironman) SayPowerLevel() {
    fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power)
}

func main() {
    b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"}
    i1 := Ironman{Hero{team: "Avengers"}, 23}

    b1.SayImBatman()
    b1.SayTeam()

    i1.SayPowerLevel()
    i1.SayTeam()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans Golang, les propriétés et méthodes exportées hors du package commencent par une lettre majuscule. Lorsque nous définissons l'acteur et l'année avec une minuscule dans le package utils, nous nous assurons qu'ils ne peuvent pas être modifiés directement. Au lieu de cela, comme vous le voyez dans le fichier main.go, vous devez utiliser les méthodes exportées (qui commencent par une lettre majuscule) - GetActor, SetActor, etc., pour les récupérer et les modifier.

C'est à cela que sert l'encapsulation : s'assurer que vous évitez les modifications accidentelles des données et fournissez plutôt des méthodes pour interagir en toute sécurité avec les données.

Une chose que vous remarquerez différente est que dans toutes les méthodes de la classe Batman, nous utilisons un récepteur de pointeur *Batman au lieu d'un récepteur de valeur Batman comme nous l'étions dans les exemples précédents. En effet, nous voulons pouvoir modifier la structure d'origine dans les méthodes Set. Et dans Golang, il est recommandé que si certaines méthodes nécessitent un récepteur de pointeur, vous fassiez en sorte que toutes les méthodes utilisent un récepteur de pointeur pour des raisons de cohérence. C'est pourquoi les méthodes Get utilisent également un récepteur de pointeur même si elles ne modifient pas la structure d'origine.

En outre, une autre chose à noter est que, simplement parce que nous utilisons un récepteur pointeur, nous n'avons pas à faire ceci : (&b1).GetActor. En Golang, les fonctions avec un argument pointeur doivent prendre un pointeur, mais les méthodes avec un récepteur pointeur peuvent prendre une valeur ou un pointeur comme récepteur.

TL;DR : Golang traduit automatiquement b1.GetActor par (&b1).GetActor puisque la méthode GetActor a un récepteur de pointeur, mais elle ne traduirait pas GetActor(b1) en GetActor(&b1) si GetActor avait été une fonction normale prenant un argument de pointeur.

Polymorphisme et abstraction

Les deux prochains piliers de la POO peuvent être matraqués car les exemples de code correspondants seraient assez similaires. Le polymorphisme fait référence à la pratique de programmation dans laquelle deux objets différents de deux classes différentes peuvent être traités comme des objets de la même superclasse commune. Cela signifie que vous pouvez appeler la même fonction sur deux objets différents comme s'il s'agissait d'objets de la même classe. Cela devrait commencer à vous donner une idée des interfaces impliquées :)

Regardons un peu de code pour mieux comprendre cela :

package main

import "fmt"

type Batman struct {
    actor string
    year int
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year)
}

func main() {
    b1 := Batman{actor: "Michael Keaton", year: 1989}
    b2 := Batman{actor: "Christian Bale", year: 2005}

    b1.SayImBatman()
    b2.SayImBatman()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans cet exemple, la fonction StartFight peut recevoir à la fois les objets b1 et i1 même s'ils ne sont en aucun cas liés les uns aux autres. Essayez de comprendre en quoi cela est différent de l'héritage, où les classes enfants avaient accès aux méthodes de la classe parent. Dans cet exemple, il n'y a pas de classes enfants et parents (et aucune méthode n'est également partagée). Au lieu de cela, deux objets différents sont traités comme identiques par une fonction : c'est ce qu'on appelle le polymorphisme.

Maintenant, cela peut également être traité comme un exemple d’abstraction. L'abstraction, comme son nom l'indique, est la pratique de programmation consistant à cacher les détails d'implémentation et à simplement fournir des fonctions qui s'occupent des choses à votre place. Dans cet exemple, vous n’avez pas besoin de vous soucier de la configuration des méthodes de chaque héros. Vous pouvez continuer à utiliser la fonction StartFight chaque fois que vous souhaitez utiliser l’une des fonctions Fight des héros. De cette façon, les détails de mise en œuvre restent cachés à l'utilisateur et seuls les détails essentiels sont exposés.

Pour en revenir maintenant au polymorphisme, il existe deux exemples plus courants, à savoir le remplacement de méthode et la surcharge.

Remplacement de méthode

Le remplacement de méthode fait référence aux classes enfants définissant leur propre implémentation des méthodes définies sur la classe parent. Cette implémentation est désormais utilisée à la place de l’implémentation de la classe parent d’origine. Prenons le code que nous avons utilisé pour l'héritage plus tôt et voyons à quoi il ressemble avec le remplacement de méthode :

package main

import "fmt"

type Hero struct {
    team string
}

type Batman struct {
    Hero
    name string
}

type Ironman struct {
    Hero
    power int
}

func (h Hero) SayTeam() {
    fmt.Println("My Team is", h.team)
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman\n", b.name)
}

func (i Ironman) SayPowerLevel() {
    fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power)
}

func main() {
    b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"}
    i1 := Ironman{Hero{team: "Avengers"}, 23}

    b1.SayImBatman()
    b1.SayTeam()

    i1.SayPowerLevel()
    i1.SayTeam()
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Le résultat de ce programme est :

//oops-in-go/utils/utils.go

package utils

type Batman struct {
    actor string
    year int
}

func (b *Batman) GetActor() string {
    return b.actor
}

func (b *Batman) GetYear() int {
    return b.year
}

func (b *Batman) SetActor(actor string) {
    b.actor = actor
}

func (b *Batman) SetYear(year int) {
    b.year = year
}
Copier après la connexion

Les objets de la classe Batman utilisent désormais leur propre méthode SayTeam au lieu de celle de la classe Hero parent. Étant donné que la classe Ironman n'a pas de méthode SayTeam propre, son objet utilise toujours la méthode de sa classe parent. C'est ce que signifie le remplacement de méthode, les classes enfants « remplaçant » les méthodes définies sur la classe parent.

Surcharge de méthode

Cela fait référence à la même fonction pouvant prendre plusieurs arguments différents. Ces arguments peuvent être différents en nombre ou en type. Golang propose deux manières d'y parvenir : via des fonctions variadiques et l'autre via des interfaces.

Regardons le code des deux, qui vous aidera à mieux comprendre :

Utilisation de fonctions variadiques

// oops-in-go/main.go

package main

import (
    "fmt"
    "oops-in-go/utils"
)

func main() {
    b1 := utils.Batman{}
    b1.SetActor("Michael Keaton")
    b1.SetYear(1989)
    fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear())

    b1.SetActor("Christian Bale")
    b1.SetYear(2005)
    fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear())
}

Copier après la connexion

Ici, vous pouvez « surcharger » la fonction listMembers avec n'importe quel nombre d'arguments.

Utilisation des interfaces

package main

import "fmt"

type Hero interface {
    Fight()
}

type Batman struct {
    weapon string
}

type Ironman struct {
    weapon string
}

func (b Batman) Fight() {
    fmt.Printf("Batman hits with a %s\n", b.weapon)
}

func (i Ironman) Fight() {
    fmt.Printf("Ironman hits with a %s\n", i.weapon)
}

func StartFight(h Hero) {
    fmt.Println("Fight has started.")
    h.Fight()
}

func main() {
    b1 := Batman{"Batarang"}
    i1 := Ironman{"Repulsor rays"}

    StartFight(b1)
    StartFight(i1)
}

Copier après la connexion

Le résultat de ce programme est :

package main

import "fmt"

type Hero struct {
    team string
}

type Batman struct {
    Hero
    name string
}

type Ironman struct {
    Hero
    power int
}

func (h Hero) SayTeam() {
    fmt.Println("My Team is", h.team)
}

func (b Batman) SayImBatman() {
    fmt.Printf("I'm %s and I'm Batman\n", b.name)
}

func (i Ironman) SayPowerLevel() {
    fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power)
}

func (b Batman) SayTeam() {
    fmt.Printf("I'm Batman and my team is %s\n", b.team)
}

func main() {
    b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"}
    i1 := Ironman{Hero{team: "Avengers"}, 23}

    b1.SayImBatman()
    b1.SayTeam()

    i1.SayPowerLevel()
    i1.SayTeam()
}

Copier après la connexion

Ici, nous « surchargeons » la méthode saySomething pour prendre des arguments de différents types. Nous prenons une interface vide comme argument, qui peut être de n'importe quel type, puis vérifions son type à l'aide d'un boîtier de commutation et imprimons la sortie en conséquence.

Conclusion

Je suis bien conscient que cette lecture a été longue, et si vous êtes resté jusqu'au bout, sachez que je suis vraiment heureux :) J'espère sincèrement que vous avez appris beaucoup de nouvelles choses sur la programmation orientée objet et comment l'implémenter dans Golang. J'écris des blogs sur différents concepts techniques sur mon site Web, et si vous souhaitez apprendre de nouvelles choses, je vous recommande de vous inscrire à ma newsletter.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal