Óxido vs. Estado

Importante : para una lectura cómoda del artículo, debe poder leer el código fuente en Rust y comprender por qué envolver todo Rc<RefCell<...>>es malo.



Introducción



Rust no se considera generalmente un lenguaje orientado a objetos: no hay herencia de implementación; a primera vista, tampoco hay encapsulado; Finalmente, los gráficos de dependencia de objetos mutables tan familiares para los adeptos de POO se ven lo más feos posible aquí (¡solo mire todos estos Rc<RefCell<...>>y Arc<Mutex<...>>!)



Es cierto que la herencia de la implementación se ha considerado dañina durante varios años y los gurús de la POO dicen cosas muy correctas como "un buen objeto es un objeto inmutable". Entonces me preguntaba: ¿Qué tan bien encajan realmente el pensamiento de objetos y el óxido ?



El primer conejillo de indias será el patrón estatal, cuya pura implementación es el tema de este artículo.



Fue elegido por una razón: un capítulo de The Rust Book está dedicado al mismo patrón . El objetivo de ese capítulo era mostrar que solo los chicos y chicas malos escriben código orientado a objetos en Rust: aquí es Optionnecesario copiar y pegar implementaciones de métodos innecesarios y triviales en todas las implementaciones del rasgo. Pero si aplica un par de trucos, todo el texto estándar desaparecerá y aumentará la legibilidad.



Escala de trabajo



El artículo original modeló el flujo de trabajo de una publicación de blog. Demostremos nuestra imaginación y adaptemos la descripción original a la dura realidad rusa:



  1. Cualquier artículo sobre Habré era una vez un borrador vacío, que el autor tenía que llenar de contenido.
  2. Cuando el artículo está listo, se envía para moderación.
  3. Tan pronto como el moderador aprueba el artículo, se publica en Habré.
  4. Hasta que se publique el artículo, los usuarios no deberían ver su contenido.


Cualquier acción ilegal con el artículo no debería tener ningún efecto (por ejemplo, no puede publicar un artículo no aprobado desde la zona de pruebas).



La siguiente lista muestra el código correspondiente a la descripción anterior.



// 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 hasta ahora se ve así:



// 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
    }
}


Esto pasa por todas las afirmaciones excepto la última. ¡No está mal!



Implementación del patrón



Agreguemos un rasgo vacío State, un estado Drafty un par de 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 con distraer diseño



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