やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのオブジェクト指向(デザインパターン)

 必ずしもRustにおいて最善とは言えない。

成果物

ステートパターン

プロジェクト作成

cargo new blog --lib

main.rs

use blog::Post;
fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!("I ate a salad for lunch today", post.content());
}

 ブログの記事。

  • 状態
    • 草案
    • 保留
    • 公開

 「公開」のときだけcontent()で値が返る。

lib.rs

雛形

pub struct Post {
    state: Option<Box<State>>,
    content: String,
}
impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }
}
trait State {}
struct Draft {}
impl State for Draft {}

Post.add_text()

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

Post.content()

impl Post {
    // --snip--
    pub fn content(&self) -> &str {
        ""
    }
}

impl State for PendingReview

impl Post {
    // --snip--
    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
}
trait State {
    fn request_review(self: Box<Self>) -> Box<State>;
}
struct Draft {}
impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<State> {
        Box::new(PendingReview {})
    }
}
struct PendingReview {}
impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<State> { self }
}

approid(), impl State for Published

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<State>;
    fn approve(self: Box<Self>) -> Box<State>;
}
struct Draft {}
impl State for Draft {
    // --snip--
    fn approve(self: Box<Self>) -> Box<State> { self }
}
struct PendingReview {}
impl State for PendingReview {
    // --snip--
    fn approve(self: Box<Self>) -> Box<State> {
        Box::new(Published {})
    }
}
struct Published {}
impl State for Published {
    fn request_review(self: Box<Self>) -> Box<State> { self }
    fn approve(self: Box<Self>) -> Box<State> { self }
}

Post.content()

impl Post {
    // --snip--
    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(&self)
    }
    // --snip--
}
  • Option::as_refで内部値の参照を返す

content()

trait State {
    // --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str { "" }
}
// --snip--
struct Published {}
impl State for Published {
    // --snip--
    fn content<'a>(&self, post: &'a Post) -> &'a str { &post.content }
}

状態と振る舞いを型としてコード化する

src/lib.rs

pub struct Post {
    content: String,
}
pub struct DraftPost {
    content: String,
}
pub struct PendingReviewPost {
    content: String,
}
impl Post {
    pub fn new() -> DraftPost {
        DraftPost {
            content: String::new(),
        }
    }
    pub fn content(&self) -> &str {
        &self.content
    }
}
impl DraftPost {
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    pub fn request_review(self) -> PendingReviewPost {
        PendingReviewPost {
            content: self.content,
        }
    }
}
impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post {
            content: self.content,
        }
    }
}

src/main.rs

use blog::Post;
fn main() {
    let mut post = Post::new();
    post.add_text("I ate a salad for lunch today");
    let post = post.request_review();
    let post = post.approve();
    assert_eq!("I ate a salad for lunch today", post.content());
}

 こっちのほうがすっきり書ける。Rustではオブジェクト指向デザインパターンを当てはめないほうがいいかもしれない。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで