Паттерны проектирования в Rust: примеры и реализация

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Паттерны проектирования в Rust с примерами

Паттерны проектирования — это проверенные решения типовых задач в разработке программного обеспечения. В Rust, благодаря его уникальной системе владения, заимствования и времени жизни, реализация классических паттернов GoF часто требует особого подхода. В этой статье мы разберём несколько популярных паттернов на примерах реального кода на Rust.

Почему Rust меняет подход к паттернам?

В отличие от языков с сборкой мусора (Java, C#), Rust накладывает строгие правила на владение памятью. Это делает некоторые классические реализации (например, Singleton через глобальное изменяемое состояние) небезопасными или неэффективными. Однако Rust предлагает свои элегантные альтернативы, основанные на типах и трейтах.

1. Builder (Строитель) — пошаговое создание объектов

Builder — один из самых часто используемых паттернов в Rust. Он идеально подходит для конфигурации сложных структур, особенно когда много опциональных полей.

Пример: создание HTTP-запроса

struct Request {
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
}

struct RequestBuilder {
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
}

impl RequestBuilder {
    fn new(method: &str, url: &str) -> Self {
        Self {
            method: method.to_string(),
            url: url.to_string(),
            headers: Vec::new(),
            body: None,
        }
    }

    fn header(mut self, key: &str, value: &str) -> Self {
        self.headers.push((key.to_string(), value.to_string()));
        self
    }

    fn body(mut self, body: &str) -> Self {
        self.body = Some(body.to_string());
        self
    }

    fn build(self) -> Request {
        Request {
            method: self.method,
            url: self.url,
            headers: self.headers,
            body: self.body,
        }
    }
}

fn main() {
    let req = RequestBuilder::new("POST", "https://api.example.com/data")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token123")
        .body(r#"{"key": "value"}"#)
        .build();

    println!("URL: {}", req.url);
    println!("Headers count: {}", req.headers.len());
}

Преимущества подхода в Rust

  • Безопасность: объект создаётся только после вызова build() — неполная конфигурация невозможна.
  • Читаемость: цепочка методов интуитивно понятна.
  • Типобезопасность: компилятор проверяет типы на каждом шаге.

2. Strategy (Стратегия) — выбор алгоритма на лету

Strategy позволяет менять поведение объекта во время выполнения. В Rust это реализуется через трейты и динамическую диспетчеризацию (Box с трейтами) или статическую (дженерики).

Пример: сортировка данных

trait SortStrategy {
    fn sort(&self, data: &mut [i32]);
}

struct BubbleSort;
struct QuickSort;

impl SortStrategy for BubbleSort {
    fn sort(&self, data: &mut [i32]) {
        // Простейшая реализация пузырьковой сортировки
        for i in 0..data.len() {
            for j in 0..data.len() - 1 - i {
                if data[j] > data[j + 1] {
                    data.swap(j, j + 1);
                }
            }
        }
    }
}

impl SortStrategy for QuickSort {
    fn sort(&self, data: &mut [i32]) {
        // Быстрая сортировка (упрощённо)
        if data.len() > 1 {
            let pivot = data.len() / 2;
            data.sort(); // Используем встроенную для примера
        }
    }
}

struct Sorter {
    strategy: Box<dyn SortStrategy>,
}

impl Sorter {
    fn new(strategy: Box<dyn SortStrategy>) -> Self {
        Sorter { strategy }
    }

    fn sort(&self, data: &mut [i32]) {
        self.strategy.sort(data);
    }
}

fn main() {
    let mut data = vec![4, 2, 9, 1, 5];

    let bubble_sorter = Sorter::new(Box::new(BubbleSort));
    bubble_sorter.sort(&mut data);
    println!("Bubble sort: {:?}", data);

    let mut data2 = vec![4, 2, 9, 1, 5];
    let quick_sorter = Sorter::new(Box::new(QuickSort));
    quick_sorter.sort(&mut data2);
    println!("Quick sort: {:?}", data2);
}

Статическая диспетчеризация через дженерики

struct GenericSorter<T: SortStrategy> {
    strategy: T,
}

impl<T: SortStrategy> GenericSorter<T> {
    fn new(strategy: T) -> Self {
        GenericSorter { strategy }
    }

    fn sort(&self, data: &mut [i32]) {
        self.strategy.sort(data);
    }
}

3. Observer (Наблюдатель) — реакция на события

В Rust для реализации Observer часто используют каналы из стандартной библиотеки (std::sync::mpsc) или слабые ссылки для предотвращения циклических ссылок.

Пример: система уведомлений

use std::sync::mpsc::{self, Sender, Receiver};
use std::thread;

trait Observer {
    fn update(&self, event: &str);
}

struct EmailNotifier;
struct SmsNotifier;

impl Observer for EmailNotifier {
    fn update(&self, event: &str) {
        println!("📧 Email sent: {}", event);
    }
}

impl Observer for SmsNotifier {
    fn update(&self, event: &str) {
        println!("📱 SMS sent: {}", event);
    }
}

struct EventManager {
    subscribers: Vec<Box<dyn Observer>>,
}

impl EventManager {
    fn new() -> Self {
        EventManager { subscribers: Vec::new() }
    }

    fn subscribe(&mut self, observer: Box<dyn Observer>) {
        self.subscribers.push(observer);
    }

    fn notify(&self, event: &str) {
        for observer in &self.subscribers {
            observer.update(event);
        }
    }
}

fn main() {
    let mut manager = EventManager::new();
    manager.subscribe(Box::new(EmailNotifier));
    manager.subscribe(Box::new(SmsNotifier));

    manager.notify("User logged in");
    manager.notify("Payment received");
}

Асинхронная версия с каналами

fn observer_with_channels() {
    let (tx, rx): (Sender<String>, Receiver<String>) = mpsc::channel();

    thread::spawn(move || {
        for received in rx {
            println!("Observed event: {}", received                

Блоги

Рекомендаций по книгам