Agregar aprobar para Cambiar el Comportamiento de content
El método aprobar será similar al método request_review: establecerá state en el valor que el estado actual dice que debe tener cuando ese estado es aprobado, como se muestra en la Lista 17-16.
Nombre de archivo: src/lib.rs
impl Post {
--snip--
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
--snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
1 self
}
}
struct PendingReview {}
impl State for PendingReview {
--snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
2 Box::new(Published {})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
Lista 17-16: Implementando el método approve en Post y el trato State
Agregamos el método approve al trato State y agregamos una nueva estructura que implementa State, el estado Published.
Similar a la forma en que funciona request_review en PendingReview, si llamamos al método approve en un Draft, no tendrá ningún efecto porque approve devolverá self [1]. Cuando llamamos a approve en PendingReview, devuelve una nueva instancia empaquetada de la estructura Published [2]. La estructura Published implementa el trato State, y para ambos métodos request_review y approve, devuelve a sí misma porque la publicación debería permanecer en el estado Published en esos casos.
Ahora necesitamos actualizar el método content en Post. Queremos que el valor devuelto por content dependa del estado actual de Post, por lo que vamos a hacer que Post delegue a un método content definido en su state, como se muestra en la Lista 17-17.
Nombre de archivo: src/lib.rs
impl Post {
--snip--
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
--snip--
}
Lista 17-17: Actualizando el método content en Post para delegar a un método content en State
Debido a que el objetivo es mantener todas estas reglas dentro de las estructuras que implementan State, llamamos a un método content en el valor en state y pasamos la instancia de la publicación (es decir, self) como argumento. Luego devolvemos el valor que se devuelve al usar el método content en el valor de state.
Llamamos al método as_ref en el Option porque queremos una referencia al valor dentro del Option en lugar de la posesión del valor. Debido a que state es un Option<Box<dyn State>>, cuando llamamos a as_ref, se devuelve un Option<&Box<dyn State>>. Si no llamáramos a as_ref, obtendríamos un error porque no podemos mover state fuera del &self prestado del parámetro de la función.
Luego llamamos al método unwrap, que sabemos que nunca causará un panic porque sabemos que los métodos en Post aseguran que state siempre contendrá un valor Some cuando esos métodos se completen. Este es uno de los casos que mencionamos en "Casos en los que Tienes Más Información que el Compilador" cuando sabemos que un valor None nunca es posible, aunque el compilador no sea capaz de entenderlo.
En este momento, cuando llamamos a content en el &Box<dyn State>, la coerción de dereferencia entrará en efecto en el & y el Box para que el método content finalmente se llame en el tipo que implementa el trato State. Eso significa que necesitamos agregar content a la definición del trato State, y ahí es donde pondremos la lógica de qué contenido devolver según el estado que tengamos, como se muestra en la Lista 17-18.
Nombre de archivo: src/lib.rs
trait State {
--snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
1 ""
}
}
--snip--
struct Published {}
impl State for Published {
--snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
2 &post.content
}
}
Lista 17-18: Agregando el método content al trato State
Agregamos una implementación predeterminada para el método content que devuelve una porción de cadena vacía [1]. Eso significa que no necesitamos implementar content en las estructuras Draft y PendingReview. La estructura Published sobrescribirá el método content y devolverá el valor en post.content [2].
Tenga en cuenta que necesitamos anotaciones de tiempo de vida en este método, como discutimos en el Capítulo 10. Estamos tomando una referencia a un post como argumento y devolviendo una referencia a una parte de ese post, por lo que el tiempo de vida de la referencia devuelta está relacionado con el tiempo de vida del argumento post.
Y ya terminamos: todo lo de la Lista 17-11 ahora funciona. Hemos implementado el patrón de estado con las reglas del flujo de trabajo de la publicación de blog. La lógica relacionada con las reglas reside en los objetos de estado en lugar de estar dispersa en todo Post.
¿Por qué No Un Enum?
Es posible que hayas estado preguntándote por qué no usamos un enum con los diferentes posibles estados de publicación como variantes. Esa ciertamente es una solución posible; pruébalo y compara los resultados finales para ver cuál prefieres. Una desventaja de usar un enum es que cada lugar que verifica el valor del enum necesitará una expresión match o similar para manejar cada posible variante. Esto podría ser más repetitivo que esta solución de objeto de trato.