Ferrugem vs. Estado

Importante : para uma leitura confortável do artigo, você precisa ser capaz de ler o código-fonte em Rust e entender por que embrulhar tudo Rc<RefCell<...>>é ruim.



Introdução



Rust geralmente não é considerada uma linguagem orientada a objetos: não há herança de implementação; à primeira vista, também não há encapsulamento; Finalmente, os gráficos de dependência de objetos mutáveis ​​tão familiares aos adeptos da OOP parecem tão feios quanto possível (basta olhar para todos eles Rc<RefCell<...>>e Arc<Mutex<...>>!)



É verdade que a herança de implementação foi considerada prejudicial por vários anos, e os gurus da POO dizem coisas muito corretas como "um bom objeto é um objeto imutável". Então eu me perguntei: como o Object Thinking e Rust realmente se encaixam ?



A primeira cobaia será o padrão de Estado, cuja implementação pura é o assunto deste artigo.



Foi escolhido por um motivo: um capítulo de The Rust Book é dedicado ao mesmo padrão . O objetivo desse capítulo era mostrar que apenas meninos e meninas maus escrevem código orientado a objetos no Rust: aqui você Optionprecisa copiar e colar implementações de métodos desnecessários e triviais em todas as implementações do traço. Mas se você aplicar alguns truques, todo o clichê desaparecerá e a legibilidade aumentará.



Escala de trabalho



O artigo original modelou o fluxo de trabalho de uma postagem de blog. Vamos mostrar nossa imaginação e adaptar a descrição original à dura realidade russa:



  1. Qualquer artigo sobre Habré já foi um rascunho vazio, que o autor teve que preencher com conteúdo.
  2. Quando o artigo estiver pronto, ele é enviado para moderação.
  3. Assim que o moderador aprovar o artigo, ele é publicado no Habré.
  4. Até que o artigo seja publicado, os usuários não devem ver seu conteúdo.


Quaisquer ações ilegais com o artigo não devem ter efeito (por exemplo, você não pode publicar um artigo não aprovado na sandbox).



A lista abaixo demonstra o código correspondente à descrição acima.



// main.rs

use article::Article;

mod article;

fn main() {
    let mut article = Article::empty();

    article.add_text("Rust    -");
    assert_eq!(None, article.content());

    article.send_to_moderators();
    assert_eq!(None, article.content());

    article.publish();
    assert_eq!(Some("Rust    -"), article.content());
}


Article até agora parece com isto:



// article/mod.rs

pub struct Article;

impl Article {
    pub fn empty() -> Self {
        Self
    }

    pub fn add_text(&self, _text: &str) {
        // no-op
    }

    pub fn content(&self) -> Option<&str> {
        None
    }

    pub fn send_to_moderators(&self) {
        // no-op
    }

    pub fn publish(&self) {
        // no-op
    }
}


Isso passa por todas as afirmações, exceto a última. Não é ruim!



Implementação do padrão



Vamos adicionar uma característica vazia State, um estado Drafte alguns campos para Article:



// article/state.rs

pub trait State {
    // empty
}

// article/states.rs

use super::state::State;

pub struct Draft;

impl State for Draft {
    // nothing
}

// article/mod.rs

use state::State;
use states::Draft;

mod state;
mod states;

pub struct Article {
    state: Box<dyn State>,
    content: String,
}

impl Article {
    pub fn empty() -> Self {
        Self {
            state: Box::new(Draft),
            content: String::new(),
        }
    }

    // ...
}


Problemas com cabeça fora Projeto



State, . , - :



trait State {
    fn send_to_moderators(&mut self) -> &dyn State;
}


, , , — .



?



pub trait State {
    fn send_to_moderators(&mut self) -> Box<dyn State>;
}


. . , ?



:



pub trait State {
    fn send_to_moderators(self: Box<Self>) -> Box<dyn State>;
}


: ( self). , Self: Sized, .. . trait object, .. .





: , , , . , , ; , .



P.S.: Amethyst.



use crate::article::Article;

pub trait State {
    fn send_to_moderators(&mut self) -> Transit {
        Transit(None)
    }
}

pub struct Transit(pub Option<Box<dyn State>>);

impl Transit {
    pub fn to(state: impl State + 'static) -> Self {
        Self(Some(Box::new(state)))
    }

    pub fn apply(self, article: &mut Article) -> Option<()> {
        article.state = self.0?;
        Some(())
    }
}


, , Draft:



// article/states.rs

use super::state::{State, Transit};

pub struct Draft;

impl State for Draft {
    fn send_to_moderators(&mut self) -> Transit {
        Transit::to(PendingReview)
    }
}

pub struct PendingReview;

impl State for PendingReview {
    // nothing
}

// article/mod.rs

impl Article {
    // ...
    pub fn send_to_moderators(&mut self) {
        self.state.send_to_moderators().apply(self);
    }
    // ...
}


-



: Published, State, publish PendingReview. Article::publish :)



. content State, Published , , Article:



// article/mod.rs

impl Article {
    // ...
    pub fn content(&self) -> Option<&str> {
        self.state.content(self)
    }
    // ...
}

// article/state.rs

pub trait State {
    // ...
    fn content<'a>(&self, _article: &'a Article) -> Option<&'a str> {
        None
    }
}

// article/states.rs

impl State for Published {
    fn content<'a>(&self, article: &'a Article) -> Option<&'a str> {
        Some(&article.content)
    }
}


, ? , !



impl Article {
    // ...
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    // ...
}


( ) , .



! !



, Article , - , , . ? , ! .





.



, - Rust , , . - -.



, , Rust . , Observer: , Arc<Mutex<...>>!



, .




All Articles