Комментарии 18
только у него есть одна проблема: в реальной жизни, в реальных
приложениях такие требования встречаются чуть чаще, нежели никогда
Так выбор сортировок это всего лишь пример применения паттерна "Стратегия". Сам паттерн используется довольно обширно, хоть и не в чистом виде. А факториалы и фиббоначи обычно показывают как работать с рекурсией в языке, так что не очень понимаю ваше негодование.
Больше меня смущает, что их называют паттернами поведения, а не программирования. Чай не про людей речь.
ну так и можно в примерах приводить нечто реалистичное, а не натянутое.
Зачем? Что-то реалистичное требует заметно больше кода, да ещё и специфичного. Кто станет читать эти примеры, если они будут размером с "Войну и Мир"?
чуть чаще чем никогда.
теории программирования утверждают что циклы и рекурсия между собой гомоморфны, так что можно утверждать, что рекурсия используется крайне часто. Опять же примеры с сериализацией будут требовать заметно больше бойлерплейта и продвинутой лексики языка в случае с растом, какой новичок будет это читать, когда у него там по два десятка констрейнтов навешано? Плюс, это паттерн "Посетитель", что вроде как вне темы статьи.
Тут в статье происходит очередное натягивание совы на глобус. Если посмотреть выше, то в impl SortStrategy for BubbleSort вообще нет никакого обращения к внутреннему состоянию BubbleSort (self). Т.е. в чем смысл присвоения трейта BubbleSort – загадка.
А зачем ему обращаться к self, если структура пустая сама по себе? Оно используется только как инстанциатор типа.
В таком случае логичнее сделать трейт BubbleSort с методом bubble_sort и имплементировать его сортируемому объекту, который тогда будет `mut self`. Но, это в моей картине мира, которую я никому не навязываю.
Трейты/интерфейсы это про описание поведения, то бишь ЧТО делать - сортировать, итерировать, манипулировать типами и прочие. Сортировка пузырьком - это конкретный инстанс поведения, то бишь КАК это делать.
Если сделать как вы предлагаете, то всё равно нужно будет для какого-нибудь типа имплементировать этот самый BubbleSort, что звучит как минимум странно.
// завидев такое могут справиться о вашем ментальном здравии
impl BubbleSort<'a T> for std::collections::HashSet<'a T> {
fn bubble_sort(&mut self) { todo!() }
}Само название трейта в статье выбрано не очень удачно, да. Лучше б было бы если б называлось что-то вроде Sortable. И интерфейс надо было void сделать, а данные в конструкторы отдавать.
В то же время существует громадное количество реализаций (например стандартные листы в C#) где стратегия все же используется как раз в сортировке.
Только задается не алгоритм сортировки, а лямбда с алгоритмом сравнения элементов.
Соответственно типичная задача - указать листу, по какому полю объекта сортировать.
Все так
Я про то, что пример такой лучше, потому что задача более прикладная.
Люди обычно используют стандартные реализации быстрой сортировки, изобретать вариации нужно только в очень редаких случаях.
А вот компаратор наоборот, задается очень часто, потому что для разных прикладных задач используются разные данные с разными правилами "что является приоритетом для сортировки в этой структуре данных"
В таком примере стратегию проще (и возможно полезнее) показать как лямбду
А ещё... Все примеры опираются на dyn trait, хотя лучше бы не опирались, код стал чище и понятнее
Ok, вы решили затащить ООП паттерны в не совсем ООП язык. Но зачем их реализовывать так прямолинейно (и насколько я понимаю, не оптимально)?
Rust предлагает более изящные и оптимальные решения, посмотреть можно здесь:
https://rust-unofficial.github.io/patterns/patterns/index.html
Вот, например, реализация стратегии без Box<dyn SortStrategy>:
use std::collections::HashMap;
type Data = HashMap<String, u32>;
trait Formatter {
fn format(&self, data: &Data, buf: &mut String);
}
struct Report;
impl Report {
// Write should be used but we kept it as String to ignore error handling
fn generate<T: Formatter>(g: T, s: &mut String) {
// backend operations...
let mut data = HashMap::new();
data.insert("one".to_string(), 1);
data.insert("two".to_string(), 2);
// generate report
g.format(&data, s);
}
}
Стратегию в Rust принято реализовывать при помощи trait-а Fn. Внутри он реализует практически тот же механизм, что и описан в статье (трейт описывающий сигнатуру метода и отдельные пустые структуры для каждой его реализации), но он является нативным типом для лямбда-функций, что позволяет использовать его намного удобнее
Когда вы пишите
"Vec<Rc<RefCell<dyn Observer>>>"
Это должно само по себе наталкивать на мысль что что-то идет не так и возможно надо найти либо другое решение либо использовать тут не Rust, а язык с GC
Краткий обзор поведенческих паттернов в Rust