Rust non ha il supporto nativo per OOP, ma puoi comunque utilizzare queste tecniche per sfruttare il paradigma.

La programmazione orientata agli oggetti (OOP) semplifica la progettazione del software enfatizzando l'uso di oggetti per rappresentare entità e concetti del mondo reale. OOP incoraggia la manutenibilità incapsulando la funzionalità all'interno degli oggetti.

Rust è un linguaggio flessibile che supporta la programmazione funzionale e procedurale. Sebbene non supporti la programmazione orientata agli oggetti in modo nativo, puoi implementare concetti OOP utilizzando i tipi di dati incorporati di Rust.

Incapsulamento in ruggine

L'incapsulamento comporta l'organizzazione del codice in unità autonome che nascondono i dettagli interni mentre esporre un'interfaccia pubblica per l'interazione esterna per ridurre al minimo la complessità e migliorare il codice manutenibilità.

Puoi incapsulare il codice Rust con i moduli. Un modulo è una raccolta di elementi che include funzioni, struct, enum e costanti. I moduli Rust forniscono funzionalità per raggruppare e definire i confini tra le parti di un programma.

instagram viewer

Utilizzo di moduli per incapsulare dati e funzioni

È possibile definire un modulo utilizzando il file mod parola chiave seguita da un nome:

mod mio_modulo {
// gli elementi del modulo vanno qui
}

Puoi organizzare i moduli in modo gerarchico nidificando le loro dichiarazioni:

mod modulo_padre {
mod mio_modulo {
// gli elementi del modulo vanno qui
}
}

È quindi possibile fare riferimento a moduli nidificati con la gerarchia completa, separando ciascun modulo con due punti doppi, ad esempio, parent_module:: mio_modulo.

Per impostazione predefinita, gli elementi all'interno dei moduli sono privati ​​e accessibili solo al codice all'interno dello stesso modulo. Ma puoi rendere pubblici i moduli usando il file pub parola chiave:

mod mio_modulo {
pubfnmia_funzione() {
// il corpo della funzione va qui
}
}

Puoi quindi accedere mia_funzione da altre parti del programma.

Usare i tratti per definire i comportamenti

Un altro modo in cui Rust consente l'incapsulamento è attraverso l'uso dei tratti. I tratti definiscono comportamenti che i tipi possono implementare e assicurano che tipi diversi siano conformi alla stessa interfaccia.

pubtrattoStampabile {
fnstampa(&se stesso);
}

pubstructIl mio tipo {
// struct campi qui
}

imp Stampabile per Il mio tipo {
fnstampa(&se stesso) {
// implementazione qui
}
}

IL Stampabile tratto ha un stampa metodo e il Il mio tipo struct implementa il Stampabile tratto implementando il stampa metodo.

Usando i tratti, puoi assicurarti che qualsiasi tipo che implementa il Stampabile tratto ha un stampa metodo. Questo è utile quando si lavora con codice generico che deve interagire con tipi diversi che condividono un comportamento comune.

Eredità a Rust

L'ereditarietà consente di definire una classe basata su un'altra. La sottoclasse erediterà le proprietà e i metodi del suo genitore.

In Rust, sei incoraggiato a usare la composizione invece dell'ereditarietà. La composizione è un processo di creazione di nuovi oggetti combinando quelli esistenti. Invece di creare una nuova classe che erediti funzionalità dalla classe base, puoi creare una nuova struttura che contenga un'istanza della struttura base e i suoi campi.

Creazione di nuovi tipi combinando tipi esistenti

Utilizzerai enum e struct per creare nuovi tipi. Le enum sono utili per i tipi con valori finiti e le strutture possono contenere più campi.

È possibile creare un tipo enum per diversi tipi di animali.

enumAnimale {
Gatto,
Cane,
Uccello,
// ...
}

In alternativa, puoi creare una struttura contenente campi per ogni tipo di animale. Le strutture possono contenere enum e altri tipi.

structAnimale {
nome: Corda,
età: u8,
tipo_animale: tipo animale,
}

enumTipo animale {
Gatto,
Cane,
Uccello,
// ...
}

IL Animale struct contiene i valori di Tipo animale tipo di enumerazione.

È possibile utilizzare i tratti per implementare l'ereditarietà e aggiungere comportamenti a un tipo senza crearne uno nuovo.

trattoVolare {
fnvolare(&se stesso);
}

Ecco come implementare il Volare tratto per più tipi.

structUccello {
nome: Corda,
apertura alare: f32,
}

imp Volare per Uccello {
fnvolare(&se stesso) {
stampa!("{} sta volando!", se stesso.nome);
}
}

structAereo {
modello: Corda,
massima velocità: u32,
}

imp Volare per Aereo {
fnvolare(&se stesso) {
stampa!("{} sta volando!", se stesso.modello);
}
}

IL Uccello E Aereo structs implementano il Volare trait e stampa le stringhe con il Stampa! macro.

Puoi chiamare il volare su entrambe le strutture senza conoscerne i tipi specifici.

fnprincipale() {
permettere uccello = Uccello {
nome: Corda::da("Aquila"),
apertura alare: 2.0,
};

permettere aereo = aereo {
modello: Corda::da("Boeing 747"),
massima velocità: 900,
};

permettere oggetti_volanti: Vecdin Vola> = vec![&uccello, &aereo];

per oggetto In oggetti_volanti {
oggetto.vola();
}
}

IL principale funzione istanzia il Aereo E Uccello tipi. IL oggetti_volanti vector è un vettore delle istanze dell'oggetto e the per ciclo attraversa il vettore e chiama il volare metodo sulle istanze.

Implementazione del polimorfismo in Rust

Una classe o un tipo è polimorfico se più tipi rappresentano un'interfaccia. Poiché i tratti forniscono la funzionalità per definire i comportamenti in Rust, fornendo allo stesso tempo un'interfaccia comune per la scrittura di codice generico, è possibile utilizzare i tratti per implementare il polimorfismo.

Ecco un tratto chiamato Disegnabile che definisce il comportamento per il rendering degli oggetti sullo schermo:

trattoDisegnabile {
fndisegno(&se stesso);
}

I tipi che implementano il tratto Drawable possono accedere a disegno funzione.

structRettangolo {
larghezza: u32,
altezza: u32,
}

imp Disegnabile per Rettangolo {
fndisegno(&se stesso) {
// Visualizza il rettangolo sullo schermo
}
}

È possibile scrivere codice generico che disegna oggetti che implementano il Disegnabile tratto.

fndraw_object(oggetto: &T) {
oggetto.draw();
}

IL draw_object funzione accetta un tipo generico T come input che implementa il Disegnabile tratto e chiama il disegno metodo sul tratto. Diversi oggetti possono implementare il Disegnabile tratto e accedere alla funzionalità.

Implementazione dell'astrazione in Rust

L'astrazione è il concetto OOP dove le classi e le interfacce sono accessibili a oggetti e tipi specificati. Puoi implementare l'astrazione in Rust con i tratti.

Ecco un tratto di esempio per un lettore multimediale:

trattoMedia {
fngiocare(&se stesso);
}

Struct ed enum che implementano il Media trait deve fornire un'implementazione per il giocare metodo.

structCanzone {
titolo: Corda,
artista: Corda,
}

imp Media per Canzone {
fngiocare(&se stesso) {
stampa!("Riproduzione brano: {} di {}", se stesso.titolo, se stesso.artista);
}
}

IL Canzone struct implementa il Media tratto fornendo un'implementazione per il giocare metodo che stampa un messaggio con i campi del Canzone struct alla console.

fnprincipale() {
// Crea un'istanza della struttura Song
permettere canzone = canzone {
titolo: Corda::da("Bohemian Rhapsody"),
artista: Corda::da("Regina"),
};

// Richiama il metodo play sull'istanza song
canzone.play();
}

IL canzone variabile è un'istanza di Canzone struct e la variabile può accedere e chiamare il giocare metodo.

Organizzare il codice Rust è facile

La programmazione orientata agli oggetti aiuta con l'organizzazione del codice. Grazie al sistema di moduli di Rust, puoi facilmente organizzare il tuo codice Rust mentre implementi i concetti OOP per la tua applicazione per mantenere il tuo codice organizzato, gestibile e intuitivo.