Pull to refresh

Comments 18

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

этот пример встречается во многих статьях для многих языков.

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

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

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

даже больше скажу — практически нет программ, которым нужно знание "что там под капотом у стандартной функции сорт"

и совершенно так же обстоит дело с факториалами и числами Фибоначчи

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

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

Так выбор сортировок это всего лишь пример применения паттерна "Стратегия". Сам паттерн используется довольно обширно, хоть и не в чистом виде. А факториалы и фиббоначи обычно показывают как работать с рекурсией в языке, так что не очень понимаю ваше негодование.

Больше меня смущает, что их называют паттернами поведения, а не программирования. Чай не про людей речь.

Так выбор сортировок это всего лишь пример применения паттерна "Стратегия".

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

А факториалы и фиббоначи обычно показывают как работать с рекурсией в языке, так что не очень понимаю ваше негодование.

Кстати, о рекурсии. Среднестатистическому программисту требуется использовать этот паттерн в той же частотности, что и переключение сортировок: чуть чаще чем никогда.

И как правило если ему доводится это делать, то рекурсия - это такая хрень, которую чаще всего приходится применять от безысходности (например язык околодекларативный, вроде SQL и в нём нет циклов, но есть рекурсивный WITH).

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

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

Зачем? Что-то реалистичное требует заметно больше кода, да ещё и специфичного. Кто станет читать эти примеры, если они будут размером с "Войну и Мир"?

чуть чаще чем никогда.

теории программирования утверждают что циклы и рекурсия между собой гомоморфны, так что можно утверждать, что рекурсия используется крайне часто. Опять же примеры с сериализацией будут требовать заметно больше бойлерплейта и продвинутой лексики языка в случае с растом, какой новичок будет это читать, когда у него там по два десятка констрейнтов навешано? Плюс, это паттерн "Посетитель", что вроде как вне темы статьи.

теории программирования утверждают что циклы и рекурсия между собой гомоморфны

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

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

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

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

И здесь мы придём к тому, что случаи, для которых предназначен этот паттерн редки, а потому как "интересное свойство языка" (факт при принятии решения о его выборе), например, его приводить нет смысла.

Тут в статье происходит очередное натягивание совы на глобус. Если посмотреть выше, то в 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 сделать, а данные в конструкторы отдавать.

Да нет, название как раз удачное, Sortable означает нечто, что можно отсортировать, а на самом деле оно само сортирует других.

В то же время существует громадное количество реализаций (например стандартные листы в C#) где стратегия все же используется как раз в сортировке.
Только задается не алгоритм сортировки, а лямбда с алгоритмом сравнения элементов.
Соответственно типичная задача - указать листу, по какому полю объекта сортировать.

Для такого есть sort_by(doc). Но оно сортировать всё равно будет timsort. Глобально на алгоритм сортировки смена компаратора не влияет.

Все так
Я про то, что пример такой лучше, потому что задача более прикладная.
Люди обычно используют стандартные реализации быстрой сортировки, изобретать вариации нужно только в очень редаких случаях.

А вот компаратор наоборот, задается очень часто, потому что для разных прикладных задач используются разные данные с разными правилами "что является приоритетом для сортировки в этой структуре данных"

В таком примере стратегию проще (и возможно полезнее) показать как лямбду

А ещё... Все примеры опираются на 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

Sign up to leave a comment.