Komposition
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 55 von 61.
Viele objektorientierte Sprachen verlassen sich stark auf Vererbung, um Funktionalität zwischen Typen zu teilen. Rust verfolgt einen anderen Ansatz: Komposition. Anstatt Verhalten von einem Elternteil zu erben, bauen Sie komplexe Typen auf, indem Sie andere Typen als Felder einbeziehen.
Stellen Sie sich die Modellierung eines Autos vor. Anstatt eine Basisklasse Vehicle zu erstellen und von dieser zu erben, setzen Sie ein Car aus kleineren, fokussierten Komponenten zusammen:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Das Car erbt nicht von Engine oder Wheels – es hat sie. Diese „hat-ein“-Beziehung ist Komposition. Sie greifen über ihre Felder auf die inneren Komponenten zu und können deren Methoden direkt aufrufen:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delegiert an die innere Komponente
}
}
Dieses Muster sorgt dafür, dass jedes Struct auf eine einzige Verantwortlichkeit fokussiert bleibt. Müssen Sie ändern, wie Motoren funktionieren? Ändern Sie nur das Engine-Struct. Möchten Sie Wheels für ein Motorrad wiederverwenden? Fügen Sie es einfach in ein neues Struct ein. Komposition bietet Ihnen Flexibilität ohne die enge Kopplung, die Vererbung oft erzeugt.
Aufgabe
EinfachLassen Sie uns ein Computersystem mittels Komposition bauen! Anstatt eine einzige massive Struktur zu erstellen, werden Sie einen Computer aus kleineren, spezialisierten Komponenten zusammensetzen – einer CPU und einer Memory. Jede Komponente wird wissen, wie sie sich selbst beschreibt, und der Computer wird die Aufgaben an seine Teile delegieren.
Sie werden Ihren Code über drei Dateien organisieren:
cpu.rs: Definieren Sie eine öffentlicheCPU-Struktur mit zwei öffentlichen Feldern:cores(u32) für die Anzahl der Kerne undspeed_ghz(f32) für die Taktrate. Fügen Sie einespecs-Methode hinzu, die einen String zurückgibt, der die CPU im Format{cores}-core @ {speed_ghz}GHzbeschreibt.memory.rs: Definieren Sie eine öffentlicheMemory-Struktur mit einem öffentlichen Feldsize_gb(u32) für die Speichergröße. Fügen Sie einespecs-Methode hinzu, die einen String im Format{size_gb}GB RAMzurückgibt.main.rs: Binden Sie beide Module ein und erstellen Sie eine öffentlicheComputer-Struktur, die eincpu-Feld vom TypCPUund einmemory-Feld vom TypMemorybesitzt. Implementieren Sie einesystem_info-Methode für den Computer, die an seine Komponenten delegiert und die vollständige System-Spezifikation ausgibt. Verwenden Sie die bereitgestellten Eingaben, um einen Computer zu bauen und seine Informationen anzuzeigen.
Die system_info-Methode sollte Folgendes ausgeben:
System: {cpu_specs} | {memory_specs}Zum Beispiel mit den Eingaben 8, 3.5 und 16:
System: 8-core @ 3.5GHz | 16GB RAMUnd mit den Eingaben 4, 2.8 und 32:
System: 4-core @ 2.8GHz | 32GB RAMSie erhalten drei Eingaben: CPU-Kerne (parsen als u32), CPU-Geschwindigkeit (parsen als f32) und Speichergröße (parsen als u32).
Spickzettel
Rust verwendet Komposition anstelle von Vererbung, um Funktionalität zwischen Typen zu teilen. Sie erstellen komplexe Typen, indem Sie andere Typen als Felder einbinden und so eine „hat-ein“-Beziehung (has-a) aufbauen.
Grundlegende Komposition
Definieren Sie kleinere, fokussierte Structs und setzen Sie diese zu größeren zusammen:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Delegieren an Komponenten
Greifen Sie über deren Felder auf innere Komponenten zu und rufen Sie deren Methoden auf:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delegate to the inner component
}
}
Komposition sorgt dafür, dass jedes Struct auf eine einzige Verantwortlichkeit fokussiert bleibt, was Flexibilität ohne enge Kopplung bietet.
Probier es selbst
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Definiere eine öffentliche Computer struct mit zwei öffentlichen Feldern:
// - cpu: CPU
// - memory: Memory
// TODO: Implementiere eine system_info Methode für Computer, die:
// - An cpu.specs() und memory.specs() delegiert
// - Folgendes ausgibt: System: {cpu_specs} | {memory_specs}
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let cores: u32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let speed_ghz: f32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let size_gb: u32 = input.trim().parse().expect("Invalid number");
// TODO: Erstelle CPU und Memory Instanzen unter Verwendung der eingelesenen Eingaben
// TODO: Erstelle eine Computer Instanz mit der CPU und Memory
// TODO: Rufe system_info() auf dem Computer auf, um das Ergebnis auszugeben
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Methoden und Verhalten
Einführung in ImplementierungsblöckeDer Self-ParameterVeränderliche MethodenAssoziierte FunktionenMehrere ImplementierungsblöckeMethod ChainingRückblick – Rechteck-Aktionen4Projekt: Virtuelles Haustier
Das Haustier definierenDas Haustier füttern7Standard-Traits
Das Debug-TraitDas Display-TraitClone und CopyGleichheits-TraitsZusammenfassung – Druckbarer Punkt10Projekt: Dokumentensystem
Das Draw-TraitText-Komponente2Kapselung und Module
Grundlagen der ModuleDas Public-SchlüsselwortPrivate FelderGetterSetterWiederholung – Sicherer Tresor5Generics
Generische StructsGenerische MethodenMehrere generische TypenGenerische FunktionenZusammenfassung - Koordinatenpunkt8Traits als Einschränkungen
Syntax für Trait BoundsMehrfache BoundsDie Where-KlauselTypen mit Traits zurückgebenRückblick – Generischer Drucker11Design Patterns in Rust
Newtype-PatternKompositionDas Drop-TraitFrom und IntoZusammenfassung – Smart Pointer Mock3Fortgeschrittene Enums
Enums mit DatenMethoden für EnumsMatching von DatenvariantenDas Option-Enum erneut betrachtetZusammenfassung – Shape-Enum6Definition von Traits
Was ist ein Trait?Traits implementierenStandardimplementierungenStandardimplementierungen überschreibenTraits mit ParameternZusammenfassung – Media Player