Komposition
Teil des Abschnitts Objektorientierte Programmierung der Rust-Journey von Coddy. Lektion 55 von 61.
Viele objektorientierte Sprachen stützen sich stark auf Vererbung, um Funktionalität zwischen Typen gemeinsam zu nutzen. Rust verfolgt einen anderen Ansatz: Komposition. Statt Verhalten von einem übergeordneten Typ zu erben, erstellst du komplexe Typen, indem du andere Typen als Felder einbindest.
Stell dir vor, du modellierst ein Auto. Anstatt eine Basisklasse Vehicle zu erstellen und von ihr zu erben, setzt du ein Car aus kleineren, spezialisierten Komponenten zusammen:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Car erbt nicht von Engine oder Wheels. Es hat sie. Diese „Hat-eine“-Beziehung ist Komposition. Du greifst über ihre fields auf die inneren Komponenten zu und kannst ihre method direkt aufrufen:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // An die innere Komponente delegieren
}
}
Dieses Muster sorgt dafür, dass jede Struktur auf eine einzige Verantwortung fokussiert bleibt. Du möchtest ändern, wie Motoren funktionieren? Ändere nur die Struktur Engine. Du möchtest Wheels für ein Motorrad wiederverwenden? Füge es einfach in eine neue Struktur ein. Komposition gibt dir Flexibilität ohne die enge Kopplung, die Vererbung oft erzeugt.
Aufgabe
EinfachLass uns mithilfe von Komposition ein Computersystem erstellen! Anstatt eine einzige massive Struktur zu erstellen, setzt du einen Computer aus kleineren, spezialisierten Komponenten zusammen: einer CPU und Memory. Jede Komponente weiß, wie sie sich selbst beschreibt, und der Computer delegiert an seine Bestandteile.
Du organisierst deinen Code über drei Dateien:
cpu.rs: Definiere eine öffentlicheCPU-Struktur mit zwei öffentlichen Feldern:cores(u32) für die Anzahl der Kerne undspeed_ghz(f32) für die Taktfrequenz. Füge einespecs-Methode hinzu, die einen String zurückgibt, der die CPU im Format{cores}-core @ {speed_ghz}GHzbeschreibt.memory.rs: Definiere eine öffentlicheMemory-Struktur mit einem öffentlichen Feldsize_gb(u32) für die Speichergröße. Füge einespecs-Methode hinzu, die einen String im Format{size_gb}GB RAMzurückgibt.main.rs: Binde beide Module ein und erstelle eine öffentlicheComputer-Struktur, die eincpu-Feld vom TypCPUund einmemory-Feld vom TypMemorybesitzt. Implementiere einesystem_info-Methode für Computer, die an seine Komponenten delegiert und die vollständigen Systemspezifikationen ausgibt. Verwende die bereitgestellten Eingaben, um einen Computer zu erstellen 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 RAMDu erhältst drei Eingaben: CPU-Kerne (als u32 parsen), CPU-Geschwindigkeit (als f32 parsen) und Speichergröße (als u32 parsen).
Probier es selbst
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Definiere eine öffentliche Computer-Struktur mit zwei öffentlichen Feldern:
// - cpu: CPU
// - memory: Memory
// TODO: Implementiere eine system_info-Methode auf Computer, die:
// - An cpu.specs() und memory.specs() delegiert
// - 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 mit den geparsten Eingaben
// TODO: Erstelle eine Computer-Instanz mit der CPU und dem 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 Objektorientierte Programmierung
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Übe selbstständig: Online-Rust-Compiler