паттерн State
От: sergii.p  
Дата: 27.02.23 10:14
Оценка: 6 (1) -1
увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.
Допустим реализуем сетевое соединение. Оно может быть либо в закрытом состоянии, либо открытом. Нам желательно в compile time запретить вызов метода close или write для ещё не открытого соединения (в классической реализации бросали исключения).
Приступим к реализации.
Сначала делаем пустые структуры для маркера состояния:
struct Open;
struct Close;

Затем реализуем собственно класс соединения:
struct Connection<State = Close> {
    ...
    state: std::marker::PhantomData<State>, // иначе получим ошибку error[E0392]: parameter `State` is never used
}

Реализуем наши методы для закрытого соединения:
impl Connection<Close> {
    fn open(self) -> Connection<Open> { todo!(); }
}

И для открытого:
impl Connection<Open> {
    fn close(self) -> Connection<Close> { todo!(); }
    fn write(&self, _data: &[u8]) { todo!(); }
}

Пишем конструктор:
impl Connection {
    fn new() -> Connection {
        Connection{ state: std::marker::PhantomData }
    }
}

Теперь всё готово для использования
fn main() {
    let con = Connection::new().open();
    con.write("Hello".to_owned().as_bytes());
    let con = con.close();
}

Об остальном позаботится компилятор. Вызвать два раза open или close невозможно (на том же C++ это запретить тяжело). Попытаться записать что-то в закрытое соединение не получится.
Остаются только философские вопросы использования. Н-р соединение может стать закрытым не по нашей воле. Или как вообще хранить объект соединение? Получается только в открытом состоянии (в закрытом возможно смысла не имеет).
То есть вопросы по сравнению с классической реализацией конечно есть, но и выгода тоже наличиствует. Лично я классически паттерн state реализовывал только однажды. После получившегося "переусложнизма" зарёкся когда-либо ещё писать подобные конструкции. Такую же технику попробовать можно. Тут дублирование минимально.

Код https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=49b593914d0a35583e64860a1b8f17e6
Оригинал на английском (и более подробно) можно послушать здесь
https://www.youtube.com/watch?v=_ccDqRTx-JU
Re: паттерн State
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 27.02.23 11:59
Оценка: +1
Здравствуйте, sergii.p, Вы писали:

SP>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.


Это не совсем тот state что gof. В других языках обычно делают класс который не надо "открывать", вся необходимая работа по инициализации выполняется в конструкторе или фабрике. Если же состояние объекта может меняться вне программы (соединение с базой например), то паттерны не помогут.
Re: паттерн State
От: alex_public  
Дата: 27.02.23 12:20
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.


Вообще то я как раз сторонник Раста и рекомендую всем (а в особенности C++'ам) переходить на него. Но конкретно тут вижу ерунду. Потому что:

1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).
2. Данный код максимально не удобен и подходит только для подобного hello world примера. Потому что в реальном коде переменная con обычно является частью какой-нибудь структуры, а при таком подходе это невозможно организовать.

Чтобы код выше был полезен к использованию, тебе придётся засунуть полученный тип (точнее каждую его специализацию) в enum (а вот это уже действительно одна из возможностей Раста, хотя и не эксклюзивная) и написать большой match под каждую ветку. Ну и кстати для записи функции переходов между типами в Расте есть специализированный типаж From (Into).
Re[2]: паттерн State
От: alex_public  
Дата: 27.02.23 12:28
Оценка:
Здравствуйте, gandjustas, Вы писали:

SP>>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.

G>Это не совсем тот state что gof. В других языках обычно делают класс который не надо "открывать", вся необходимая работа по инициализации выполняется в конструкторе или фабрике. Если же состояние объекта может меняться вне программы (соединение с базой например), то паттерны не помогут.

Здесь под State явно подразумевался конечный автомат. )))
Re: паттерн State
От: johny5 Новая Зеландия
Дата: 28.02.23 01:44
Оценка:
Здравствуйте, sergii.p, Вы писали:


SP>
SP>impl Connection {
SP>    fn new() -> Connection {
SP>        Connection{ state: std::marker::PhantomData }
SP>    }
SP>}
SP>


А почему здесь можно опустить указание возвращаемого типа?
fn new() -> Connection<Close> { ..


помоему совсем неочевидно какой тип должен возвращаться.
Re[2]: паттерн State
От: sergii.p  
Дата: 28.02.23 06:22
Оценка: 6 (1)
Здравствуйте, johny5, Вы писали:

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


там тип по-умолчанию. Меня тоже сначала в ролике сбило это с толку
struct Connection<State = Close>
Re[2]: паттерн State
От: sergii.p  
Дата: 28.02.23 08:50
Оценка:
Здравствуйте, alex_public, Вы писали:

_>1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).


отдельные типы будут дублировать данные (в ролике эта проблема описана). С++ не сможет обеспечить "единственность" вызова open.

_>2. Данный код максимально не удобен и подходит только для подобного hello world примера. Потому что в реальном коде переменная con обычно является частью какой-нибудь структуры, а при таком подходе это невозможно организовать.


я это и говорил. Применение спорное. Но мне кажется вы драматизируете. Всё отлично можно применять, только не надо пытаться реализовать костыли из ООП на расте.
Поле класса Connection вам собственно вообще не нужно. Вы обычно спрашиваете ConnectionPool: "дай мне открытое соединение. Не, закрытое не давай. Сразу открытое давай!"

struct ConnectionPool {
connections: std::Vec<Connection<Open>>,
}

а ConnectionPool по необходимости лезет в настройки, вытаскивает закрытое соединение и открывает его.

struct Settings;

impl Settings {
   fn get_con(...) -> Connection<Close>{ todo!(); }
}

impl ConnectionPool {
   fn get_con(& mut) -> Connection<Open> { todo!(); }
}


то есть всё можно, просто мы ещё мыслим терминами ООП, тогда как Rust легонько бьёт по рукам за такой подход (в то время как haskell бы просто убил).
Re: паттерн State
От: σ  
Дата: 28.02.23 09:42
Оценка:
SP>увидел довольно оригинальную технику реализации паттерна State

Оригинальная техника — это обозначать состояние типом? Вроде давно известная, и появилась вне/до руста.
Re[3]: паттерн State
От: night beast СССР  
Дата: 28.02.23 10:26
Оценка:
Здравствуйте, sergii.p, Вы писали:

_>>1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).


SP>отдельные типы будут дублировать данные (в ролике эта проблема описана). С++ не сможет обеспечить "единственность" вызова open.


а можно подробнее суть проблемы?
в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"
Re[4]: паттерн State
От: sergii.p  
Дата: 28.02.23 15:13
Оценка:
Здравствуйте, night beast, Вы писали:

NB>а можно подробнее суть проблемы?

NB>в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"

С++ не может обеспечить единственность вызова open из-за use after move
т. е. такой код на плюсах скомпилируется

Connection<Close> con{};
con.open();
con.open();

rust понятно выдаст ошибку на второй вызов. Можно извращаться конечно c rvalue ссылкой
struct Connection {
    void open() && {}
};

но всё это как-то искусственно. И нисколько не гарантирует от второго вызова. Джун ведь генерирует рандомный код, пока компилятор не перестаёт ругаться. Вот с какой-то итерации он таки хакнет матрицу

Connection<Close> con{};
std::move(con).open();
std::move(con).open();



Момент с дублированием проявляется в следующем примере
struct OpenedConnection {
    ip: String,
    port: u32,
    is_ssl: bool,
}

struct ClosedConnection {
    ip: String,
    port: u32,
    is_ssl: bool,
}


т е просто дублируем поля, которые можно и не дублировать. Конечно если разные состояния должны хранить разный набор полей, то это и не проблема. Но зачастую всё же поля повторяются. Можно завести отдельную структуру, где перечислить все повторяющиеся поля, но это уже не такое чистое решение. Чем же мне решение на rust и приглянулось. Взгляните на классический паттерн State по GoF. Один класс на фасад, один интерфейс и по одному классу на каждое состояние. Минимум 4 сущности. Только это превращает классический State в антипаттерн.
Re[5]: паттерн State
От: · Великобритания  
Дата: 28.02.23 15:34
Оценка: +1
Здравствуйте, sergii.p, Вы писали:


SP>Момент с дублированием проявляется в следующем примере

SP>
SP>struct OpenedConnection {
SP>    ip: String,
SP>    port: u32,
SP>    is_ssl: bool,
SP>}

SP>struct ClosedConnection {
SP>    ip: String,
SP>    port: u32,
SP>    is_ssl: bool,
SP>}
SP>


SP>т е просто дублируем поля, которые можно и не дублировать. Конечно если разные состояния должны хранить разный набор полей, то это и не проблема. Но зачастую всё же поля повторяются. Можно завести отдельную структуру, где перечислить все повторяющиеся поля, но это уже не такое чистое решение. Чем же мне решение на rust и приглянулось. Взгляните на классический паттерн State по GoF. Один класс на фасад, один интерфейс и по одному классу на каждое состояние. Минимум 4 сущности. Только это превращает классический State в антипаттерн.

Тут просто корявая модель в ООП. Классическая ошибка — использование наследования-генериков вместо mixin. Ведь у тебя есть два абсолютно разных типа (а тип с разными параметрами — это и есть два типа), то их и нужно явно описывать. Что-то вроде
struct ConnectionDetails {
    ip: String,
    port: u32,
    is_ssl: bool,
}

struct Connection {
    details: ConnectionDetails, // опционально, можно и не хранить.
    bytesSent: u32,
    bytesReceived: u32,
...
}

И тогда проблем с повторным вызовом open нет, и вообще open не нужен — открытие происходит в конструкторе Connection, который на вход берёт ConnectionDetails.
И генерики не нужны. KISS.

ЗЫЖ Твоя штука никакого отношения к gof State не имеет. State — это всё-таки динамическое состояние. Когда внезапно сокет может закрыться, напирмер, а у тебя событие по таймеру, или из бд что-то загрузилось и в зависимости от того чего загрузилось надо делать что-то немного разное. compile time тут никаким боком.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 28.02.2023 15:43 · . Предыдущая версия .
Re[5]: паттерн State
От: night beast СССР  
Дата: 28.02.23 16:44
Оценка:
Здравствуйте, sergii.p, Вы писали:

NB>>а можно подробнее суть проблемы?

NB>>в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"

SP>С++ не может обеспечить единственность вызова open из-за use after move

SP>т. е. такой код на плюсах скомпилируется

а, ты об этом. можно было проще:
let x = Box::new(0);
drop(x);
println("{}", x);

здесь тоже плюсы тебя не обругают.

SP>
SP>struct Connection {
SP>    void open() && {}
SP>}; 
SP>


&& не помогут. в с++ нельзя менять область видимости переменной.

SP>Момент с дублированием проявляется в следующем примере

SP>
SP>struct OpenedConnection {
SP>}

SP>struct ClosedConnection {
SP>}
SP>


это вообще ерунда. вынеси в общую базу или в доп.переменную, как сказали ранее.

SP>Взгляните на классический паттерн State по GoF. Один класс на фасад, один интерфейс и по одному классу на каждое состояние. Минимум 4 сущности. Только это превращает классический State в антипаттерн.


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