Menu
Coddy logo textTech

Композиция

Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 55 из 61.

Многие объектно-ориентированные языки в значительной степени полагаются на наследование для совместного использования функциональности между типами. Rust использует другой подход: композицию. Вместо того чтобы наследовать поведение от родителя, вы создаете сложные типы, включая другие типы в качестве полей.

Рассмотрим моделирование автомобиля. Вместо того чтобы создавать базовый класс Vehicle и наследоваться от него, вы компонуете Car из более мелких, специализированных компонентов:

struct Engine {
    horsepower: u32,
}

struct Wheels {
    count: u8,
    diameter: f32,
}

struct Car {
    engine: Engine,
    wheels: Wheels,
    brand: String,
}

Car не наследуется от Engine или Wheels — он содержит их. Это отношение «has-a» (имеет) является композицией. Вы получаете доступ к внутренним компонентам через их поля и можете напрямую вызывать их методы:

impl Engine {
    fn start(&self) {
        println!("Engine with {} HP started!", self.horsepower);
    }
}

impl Car {
    fn start(&self) {
        self.engine.start();  // Делегирование внутреннему компоненту
    }
}

Этот паттерн позволяет каждой структуре сохранять узкую специализацию. Нужно изменить принцип работы двигателей? Измените только структуру Engine. Хотите повторно использовать Wheels для мотоцикла? Просто включите её в новую структуру. Композиция дает вам гибкость без жесткой связи, которую часто создает наследование.

challenge icon

Задание

Легко

Давайте создадим компьютерную систему, используя композицию! Вместо того чтобы создавать одну массивную структуру, вы соберете Computer из более мелких, специализированных компонентов — CPU и Memory. Каждый компонент будет знать, как описать себя, а Computer будет делегировать задачи своим частям.

Вы организуете свой код в трех файлах:

  • cpu.rs: Определите публичную структуру CPU с двумя публичными полями: cores (u32) для количества ядер и speed_ghz (f32) для тактовой частоты. Добавьте метод specs, который возвращает String, описывающую процессор в формате "{cores}-core @ {speed_ghz}GHz".
  • memory.rs: Определите публичную структуру Memory с публичным полем size_gb (u32) для объема памяти. Добавьте метод specs, который возвращает String в формате "{size_gb}GB RAM".
  • main.rs: Подключите оба модуля и создайте публичную структуру Computer, которая имеет поле cpu типа CPU и поле memory типа Memory. Реализуйте метод system_info для Computer, который делегирует выполнение своим компонентам и выводит полную спецификацию системы. Используйте предоставленные входные данные для создания Computer и отображения информации о нем.

Метод system_info должен выводить:

System: {cpu_specs} | {memory_specs}

Например, при входных данных 8, 3.5 и 16:

System: 8-core @ 3.5GHz | 16GB RAM

И при входных данных 4, 2.8 и 32:

System: 4-core @ 2.8GHz | 32GB RAM

Вы получите три входных значения: количество ядер процессора (преобразовать в u32), частоту процессора (преобразовать в f32) и объем памяти (преобразовать в u32).

Шпаргалка

Rust использует композицию вместо наследования для совместного использования функциональности между типами. Вы создаете сложные типы, включая другие типы в качестве полей, создавая отношение «имеет» (has-a).

Базовая композиция

Определяйте небольшие, специализированные структуры и объединяйте их в более крупные:

struct Engine {
    horsepower: u32,
}

struct Wheels {
    count: u8,
    diameter: f32,
}

struct Car {
    engine: Engine,
    wheels: Wheels,
    brand: String,
}

Делегирование компонентам

Обращайтесь к внутренним компонентам через их поля и вызывайте их методы:

impl Engine {
    fn start(&self) {
        println!("Engine with {} HP started!", self.horsepower);
    }
}

impl Car {
    fn start(&self) {
        self.engine.start();  // Делегирование внутреннему компоненту
    }
}

Композиция позволяет каждой структуре сосредоточиться на одной задаче, обеспечивая гибкость без жесткой связи.

Попробуйте сами

mod cpu;
mod memory;

use cpu::CPU;
use memory::Memory;

// TODO: Определите публичную структуру Computer с двумя публичными полями:
// - cpu: CPU
// - memory: Memory

// TODO: Реализуйте метод system_info для Computer, который:
// - Делегирует выполнение cpu.specs() и memory.specs()
// - Выводит: 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: Создайте экземпляры CPU и Memory, используя полученные входные данные
    
    // TODO: Создайте экземпляр Computer с CPU и Memory
    
    // TODO: Вызовите system_info() у компьютера, чтобы вывести результат
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming