重要:記事を快適に読むには、Rustのソースコードを読み、すべてをラップすることRc<RefCell<...>>
が悪い理由を理解できる必要があります。
前書き
Rustは通常、オブジェクト指向の言語とは見なされません。実装の継承はありません。一見したところ、カプセル化もありません。最後に、OOP-達人にとても身近な変更可能なオブジェクトの依存関係のグラフは、できるだけ醜いように見える(ただ、これらすべてを見てRc<RefCell<...>>
とArc<Mutex<...>>
!)
確かに、実装の継承はすでに数年前から有害であると考えられており、OOPの専門家は、「良いオブジェクトは不変のオブジェクトである」などの非常に正しいことを言っています。だから私は疑問に思いました:オブジェクト思考と錆は本当にどれくらいうまく調和していますか?
最初のギニアピッグはStateパターンであり、その純粋な実装がこの記事の主題です。
それが選ばれた理由は、The RustBookの章が同じパターンに捧げられているからです。この章の目的は、悪い男の子と女の子だけがRustでオブジェクト指向のコードを書くことを示すことでした。ここでは、不要Option
なメソッドの実装と些細なメソッドの実装の両方をコピーして、特性のすべての実装に貼り付ける必要があります。しかし、いくつかのトリックを適用すると、ボイラープレート全体が消え、読みやすさが向上します。
仕事の規模
元の記事は、ブログ投稿のワークフローをモデル化したものです。私たちの想像力を示し、元の説明を厳しいロシアの現実に適合させましょう。
- Habréに関する記事は、かつては空のドラフトであり、作成者はコンテンツを記入する必要がありました。
- 記事の準備ができたら、モデレートのために送信されます。
- モデレーターが記事を承認するとすぐに、Habréに公開されます。
- 記事が公開されるまで、ユーザーはそのコンテンツを見ることができません。
記事に対する違法行為は効果がないはずです(たとえば、サンドボックスから未承認の記事を公開することはできません)。
以下のリストは、上記の説明に対応するコードを示しています。
// 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
これまでのところ、次のようになります。
// 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
}
}
これは、最後のアサートを除くすべてのアサートを通過します。悪くない!
パターンの実装
空の特性State
、状態Draft
、およびいくつかのフィールドを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(),
}
}
// ...
}
とのトラブル 頭を下げる 設計
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<...>>
!
, .