увидел довольно оригинальную технику реализации паттерна 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
}
fn main() {
let con = Connection::new().open();
con.write("Hello".to_owned().as_bytes());
let con = con.close();
}
Об остальном позаботится компилятор. Вызвать два раза open или close невозможно (на том же C++ это запретить тяжело). Попытаться записать что-то в закрытое соединение не получится.
Остаются только философские вопросы использования. Н-р соединение может стать закрытым не по нашей воле. Или как вообще хранить объект соединение? Получается только в открытом состоянии (в закрытом возможно смысла не имеет).
То есть вопросы по сравнению с классической реализацией конечно есть, но и выгода тоже наличиствует. Лично я классически паттерн state реализовывал только однажды. После получившегося "переусложнизма" зарёкся когда-либо ещё писать подобные конструкции. Такую же технику попробовать можно. Тут дублирование минимально.
Здравствуйте, sergii.p, Вы писали:
SP>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.
Это не совсем тот state что gof. В других языках обычно делают класс который не надо "открывать", вся необходимая работа по инициализации выполняется в конструкторе или фабрике. Если же состояние объекта может меняться вне программы (соединение с базой например), то паттерны не помогут.
Здравствуйте, sergii.p, Вы писали:
SP>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить.
Вообще то я как раз сторонник Раста и рекомендую всем (а в особенности C++'ам) переходить на него. Но конкретно тут вижу ерунду. Потому что:
1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).
2. Данный код максимально не удобен и подходит только для подобного hello world примера. Потому что в реальном коде переменная con обычно является частью какой-нибудь структуры, а при таком подходе это невозможно организовать.
Чтобы код выше был полезен к использованию, тебе придётся засунуть полученный тип (точнее каждую его специализацию) в enum (а вот это уже действительно одна из возможностей Раста, хотя и не эксклюзивная) и написать большой match под каждую ветку. Ну и кстати для записи функции переходов между типами в Расте есть специализированный типаж From (Into).
Здравствуйте, gandjustas, Вы писали:
SP>>увидел довольно оригинальную технику реализации паттерна State на расте. На других языках такое городить тоже можно, но результат может не воодушевить. G>Это не совсем тот state что gof. В других языках обычно делают класс который не надо "открывать", вся необходимая работа по инициализации выполняется в конструкторе или фабрике. Если же состояние объекта может меняться вне программы (соединение с базой например), то паттерны не помогут.
Здесь под State явно подразумевался конечный автомат. )))
Здравствуйте, alex_public, Вы писали:
_>1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).
отдельные типы будут дублировать данные (в ролике эта проблема описана). С++ не сможет обеспечить "единственность" вызова open.
_>2. Данный код максимально не удобен и подходит только для подобного hello world примера. Потому что в реальном коде переменная con обычно является частью какой-нибудь структуры, а при таком подходе это невозможно организовать.
я это и говорил. Применение спорное. Но мне кажется вы драматизируете. Всё отлично можно применять, только не надо пытаться реализовать костыли из ООП на расте.
Поле класса Connection вам собственно вообще не нужно. Вы обычно спрашиваете ConnectionPool: "дай мне открытое соединение. Не, закрытое не давай. Сразу открытое давай!"
Здравствуйте, sergii.p, Вы писали:
_>>1. Никакие специфические особенности Раста тут не используются. Это элементарно может повторить любой статический язык. Причём даже не такой крутой как C++ (где это дословно переписывается), а скажем какая-нибудь убогая Java это легко реализует (просто берём не обобщённые типы, а отдельные — это по сути ничего не изменит).
SP>отдельные типы будут дублировать данные (в ролике эта проблема описана). С++ не сможет обеспечить "единственность" вызова open.
а можно подробнее суть проблемы?
в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"
Здравствуйте, night beast, Вы писали:
NB>а можно подробнее суть проблемы? NB>в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"
С++ не может обеспечить единственность вызова open из-за use after move
т. е. такой код на плюсах скомпилируется
Connection<Close> con{};
con.open();
con.open();
rust понятно выдаст ошибку на второй вызов. Можно извращаться конечно c rvalue ссылкой
struct Connection {
void open() && {}
};
но всё это как-то искусственно. И нисколько не гарантирует от второго вызова. Джун ведь генерирует рандомный код, пока компилятор не перестаёт ругаться. Вот с какой-то итерации он таки хакнет матрицу
т е просто дублируем поля, которые можно и не дублировать. Конечно если разные состояния должны хранить разный набор полей, то это и не проблема. Но зачастую всё же поля повторяются. Можно завести отдельную структуру, где перечислить все повторяющиеся поля, но это уже не такое чистое решение. Чем же мне решение на rust и приглянулось. Взгляните на классический паттерн State по GoF. Один класс на фасад, один интерфейс и по одному классу на каждое состояние. Минимум 4 сущности. Только это превращает классический State в антипаттерн.
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 тут никаким боком.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, sergii.p, Вы писали:
NB>>а можно подробнее суть проблемы? NB>>в упор не вижу чего-то необычного и что "С++ не сможет обеспечить"
SP>С++ не может обеспечить единственность вызова open из-за use after move SP>т. е. такой код на плюсах скомпилируется
это вообще ерунда. вынеси в общую базу или в доп.переменную, как сказали ранее.
SP>Взгляните на классический паттерн State по GoF. Один класс на фасад, один интерфейс и по одному классу на каждое состояние. Минимум 4 сущности. Только это превращает классический State в антипаттерн.
классический State спокойно работает в рантайме.
то что здесь показано на State не особо тянет.