Как стать автором
Обновить
1018.67
OTUS
Цифровые навыки от ведущих экспертов

Краткий обзор поведенческих паттернов в Rust

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров4.6K

Привет, Хабр!

Зачем нужны поведенческие паттерны? Вопрос риторический, но ответ на него кроется в самой сути разработки. Поведенческие паттерны предоставляют нам для решения часто встречающихся проблем взаимодействия между объектами и классами.

Паттерны можно реализовать на любом ЯПе и сегодня рассмотрим, как их реализовать в Rust.

Паттерн strategy

Паттерн стратегия позволяет определить семейство алгоритмов, инкапсулировать каждый из них и сделать их взаимозаменяемыми. Стратегия позволяет алгоритму изменяться независимо от клиентов, которые его используют.

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

  • нужно использовать различные варианты поведения в зависимости от контекста.

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

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

Реализация в Rust

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

Определим трейт:

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

Определяем интерфейс для всех стратегий сортировки. Любая структура, реализующая этот трейт, должна предоставить метод sort, который принимает изменяемый срез i32 и сортирует его.

Реализуем две стратегии сортировки, пузырьковую и быструю.

Пузырьковая:

struct BubbleSort;

impl SortStrategy for BubbleSort {
    fn sort(&self, data: &mut [i32]) {
        let mut n = data.len();
        let mut swapped = true;

        while swapped {
            swapped = false;
            for i in 1..n {
                if data[i - 1] > data[i] {
                    data.swap(i - 1, i);
                    swapped = true;
                }
            }
            n -= 1;
        }
    }
}

Быстрая:

struct QuickSort;

impl SortStrategy for QuickSort {
    fn sort(&self, data: &mut [i32]) {
        quick_sort(data, 0, data.len() as i32 - 1);
    }
}

fn quick_sort(data: &mut [i32], low: i32, high: i32) {
    if low < high {
        let pi = partition(data, low, high);
        quick_sort(data, low, pi - 1);
        quick_sort(data, pi + 1, high);
    }
}

fn partition(data: &mut [i32], low: i32, high: i32) -> i32 {
    let pivot = data[high as usize];
    let mut i = low - 1;

    for j in low..high {
        if data[j as usize] < pivot {
            i += 1;
            data.swap(i as usize, j as usize);
        }
    }
    data.swap((i + 1) as usize, high as usize);
    i + 1
}

Теперь их можно юзать в контексте:

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

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

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

fn main() {
    let mut data = vec![3, 1, 4, 1, 5, 9, 2, 6];
    let context = Context::new(Box::new(QuickSort));

    context.sort(&mut data);
    println!("{:?}", data);
}

Паттерн observer

Observer позволяет объектам подписываться на события других объектов и реагировать на них в реал тайме.

События происходят постоянно: пользовательский ввод, изменения в данных, сигналы от внешних сервисов и так далее. Observer позволяет создавать модульные, легко расширяемые приложения.

Реализация в Rust

Rust предлагает трейты, умные указатели и каналы для реализации всего этого.

Определим трейт Observer и Subject:

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

trait Subject {
    fn subscribe(&mut self, observer: Rc<RefCell<dyn Observer>>);
    fn unsubscribe(&mut self, observer: Rc<RefCell<dyn Observer>>);
    fn notify_observers(&self);
}

Реализуем конкретный наблюдатель:

struct ConcreteObserver {
    name: String,
}

impl Observer for ConcreteObserver {
    fn update(&self, message: &str) {
        println!("{} received: {}", self.name, message);
    }
}

Реализация конкретного субъекта:

struct ConcreteSubject {
    observers: Vec<Rc<RefCell<dyn Observer>>>,
}

impl ConcreteSubject {
    fn new() -> Self {
        ConcreteSubject {
            observers: Vec::new(),
        }
    }
}

impl Subject for ConcreteSubject {
    fn subscribe(&mut self, observer: Rc<RefCell<dyn Observer>>) {
        self.observers.push(observer);
    }

    fn unsubscribe(&mut self, observer: Rc<RefCell<dyn Observer>>) {
        let index = self.observers.iter().position(|x| Rc::ptr_eq(x, &observer));
        if let Some(index) = index {
            self.observers.remove(index);
        }
    }

    fn notify_observers(&self) {
        for observer in self.observers.iter() {
            observer.borrow().update("Event happened");
        }
    }
}

Использование:

fn main() {
    let subject = Rc::new(RefCell::new(ConcreteSubject::new()));

    let observer1 = Rc::new(RefCell::new(ConcreteObserver { name: "Observer 1".to_string() }));
    let observer2 = Rc::new(RefCell::new(ConcreteObserver { name: "Observer 2".to_string() }));

    subject.borrow_mut().subscribe(observer1.clone());
    subject.borrow_mut().subscribe(observer2.clone());

    subject.borrow().notify_observers();

    subject.borrow_mut().unsubscribe(observer2);

    subject.borrow().notify_observers();
}

ConcreteSubjectможет уведомлять ConcreteObserver'ы о событиях. Rc<RefCell<T>> юзается для управления владением и изменяемостью наблюдателей.

Паттерн mediator

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

Основная цель паттерна это снижение связности между компонентами системы, что достигается за счет перемещения логики взаимодействия в один централизованный компонент

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

Сначала определим трейт Mediator, который будет описывать общий интерфейс для посредника:

trait Mediator {
    fn send(&self, message: &str, sender: &str);
}

Компоненты будут взаимодействовать с посредником, но не напрямую друг с другом:

struct User<'a> {
    name: String,
    mediator: &'a dyn Mediator,
}

impl<'a> User<'a> {
    fn send(&self, message: &str) {
        self.mediator.send(message, &self.name);
    }

    fn receive(&self, message: &str, sender: &str) {
        println!("{} received a message from {}: {}", self.name, sender, message);
    }
}

Посредник, который будет управлять взаимодействием между пользователям:

struct ChatMediator {
    users: Vec<User>,
}

impl ChatMediator {
    fn new() -> Self {
        ChatMediator { users: vec![] }
    }

    fn add_user(&mut self, user: User) {
        self.users.push(user);
    }
}

impl Mediator for ChatMediator {
    fn send(&self, message: &str, sender: &str) {
        for user in &self.users {
            if user.name != sender {
                user.receive(message, sender);
            }
        }
    }
}

Использование:

fn main() {
    let mediator = ChatMediator::new();

    let user1 = User { name: "Alice".to_string(), mediator: &mediator };
    let user2 = User { name: "Bob".to_string(), mediator: &mediator };

    mediator.add_user(user1);
    mediator.add_user(user2);

    user1.send("Hi, Bob!");
    user2.send("Hello, Alice!");
}

Паттерн state

State позволяет изменять поведение в зависимости от внутреннего состояния. Иными словами, объект кажется меняющим свой класс.

В основе паттерна лежит идея делегирования задач от основного объекта к объектам, представляющим конкретные состояния. Эти объекты состояний обычно следуют общему интерфейсу, который определяет методы, через которые основной объект взаимодействует с ними. Переключение между состояниями осуществляется путем замены текущего объекта состояния на другой, что влияет на поведение основного объекта без необходимости изменения его кода.

trait State {
    fn do_action(&self, context: &mut Context);
}

struct StartState;

impl State for StartState {
    fn do_action(&self, context: &mut Context) {
        println!("Player is in start state");
        context.set_state(Box::new(StopState));
    }
}

struct StopState;

impl State for StopState {
    fn do_action(&self, context: &mut Context) {
        println!("Player is in stop state");
        context.set_state(Box::new(StartState));
    }
}

struct Context {
    state: Option<Box<dyn State>>,
}

impl Context {
    fn new() -> Context {
        Context { state: None }
    }

    fn set_state(&mut self, state: Box<dyn State>) {
        self.state = Some(state);
    }

    fn do_action(&mut self) {
        if let Some(ref state) = self.state {
            state.do_action(self);
        }
    }
}

fn main() {
    let mut context = Context::new();

    let start_state = StartState;
    start_state.do_action(&mut context);

    context.do_action();
    context.do_action();
}

Context хранит текущее состояние, которое может быть либо StartState, либо StopState. Каждое состояние знает, как переключить контекст в другое состояние, демонстрируя динамическое изменение поведения объекта Context в зависимости от его состояния.


Каждый паттерн предлагает решение для конкретной проблемы. Всего счастливого!

Больше про паттерны и языки программирования в целом, вы можете узнать в рамках онлайн-курсов от практикующих экспертов. В каталоге курсов OTUS все заинтересованные смогут найти подходящее направление, а в календаре мероприятий зарегистрироваться на предложенные бесплатные вебинары.

Теги:
Хабы:
Всего голосов 16: ↑11 и ↓5+8
Комментарии18

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS