Паттерны проектирования в 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