Композиция
Часть раздела Объектно-ориентированное программирование путешествия по 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. Он содержит их. Это отношение «имеет» называется композицией. Вы получаете доступ к внутренним компонентам через их поля и можете напрямую вызывать их методы:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Делегировать внутреннему компоненту
}
}
Этот шаблон позволяет сосредоточить каждую структуру на одной ответственности. Нужно изменить принцип работы двигателей? Измените только структуру Engine. Хотите повторно использовать Wheels для мотоцикла? Просто включите её в новую структуру. Композиция обеспечивает гибкость без тесной связанности, которую часто создаёт наследование.
Задание
ЛегкоДавайте создадим компьютерную систему с использованием композиции! Вместо создания одной огромной структуры вы составите Computer из небольших специализированных компонентов: CPU и Memory. Каждый компонент будет знать, как описать себя, а Computer будет делегировать работу своим частям.
Вы организуете код в трёх файлах:
cpu.rs: Define публичную структуруCPUс двумя публичными полями:cores(u32) для number ядер иspeed_ghz(f32) для тактовой частоты. Добавьтеspecsmethod, который returns String с описанием CPU в формате{cores}-core @ {speed_ghz}GHz.memory.rs: Define публичную структуруMemoryс публичным полемsize_gb(u32) для размера памяти. Добавьтеspecsmethod, который returns String в формате{size_gb}GB RAM.main.rs: Подключите оба модуля и создайте публичную структуруComputer, которая has полеcpuтипаCPUи полеmemoryтипаMemory. Implementsystem_infomethod для Computer, который делегирует работу своим компонентам и выводит полную спецификацию системы. Используйте предоставленные inputs, чтобы создать Computer и отобразить его информацию.
Method system_info должен выводить:
System: {cpu_specs} | {memory_specs}Например, для inputs 8, 3.5 и 16:
System: 8-core @ 3.5GHz | 16GB RAMА для inputs 4, 2.8 и 32:
System: 4-core @ 2.8GHz | 32GB RAMВы получите три входных значения: количество ядер CPU (parse как u32), скорость CPU (parse как f32) и размер памяти (parse как u32).
Попробуйте сами
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() у computer, чтобы вывести результат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media PlayerПотренируйтесь самостоятельно: Онлайн-компилятор Rust