κeenのHappy Hacκing Blog | Lispエイリアンの狂想曲

Rust風にデザインパターン23種

κeenです。 GoFのデザインパターンは有名ですが、言語機能によっては単純化できたりあるいは不要だったりするのでRust風に書き換えたらどうなるか試してみます。 発端はこのツイート。

一応誤解のないように説明しておくと、該当のQiitaの記事に不満がある訳ではなくてGoFのデザインパターンついての言及です。

リンク先のコードで十分な時にはここでは流すのでリンク先も同時に参照下さい。 また、比較しやすいようにサンプルコードはリンク先のものに則って書きます。

一応デザインパターンの教科書として結城先生の本を参照しつつ書いていきます。

Command パターン

列挙型を使うところです。 Javaにまともなenumがないのでクラスが使われていますが、列挙型があるならそちらの方がデータとコードの分離ができて見通しがいいです。また、コマンドが1つの型に収まるのでトレイトオブジェクトを作る必要がなくなります。

比較的マイナーな変更です。

Lispだとクロージャで解決したりしますが、Rustだと列挙型の方がしっくりくるかなと思います。

trait Command<T> {
    fn execute(&self, &mut T);
    fn undo(&self, &mut T);
}



struct Invoker<'a, Cmd, T: 'a> {
    commands: Vec<Cmd>,
    target: &'a mut T,
    current_index: usize,
}


impl<'a, Cmd, T> Invoker<'a, Cmd, T> {
    fn new(t: &'a mut T) -> Self {
        Invoker {
            commands: Vec::new(),
            target: t,
            current_index: 0,
        }
    }


    fn target(&self) -> &T {
        self.target
    }

    fn append_command(&mut self, c: Cmd) {
        self.commands.push(c);
    }
}

impl<'a, Cmd, T> Invoker<'a, Cmd, T>
    where Cmd: Command<T>
{
    fn execute_command(&mut self) {
        if self.commands.len() <= self.current_index {
            // Nothing to do.
            return;
        }

        let c = &self.commands[self.current_index];
        let t = &mut *self.target;
        c.execute(t);

        self.current_index += 1;
    }

    fn execute_all_commands(&mut self) {
        for _ in self.current_index..self.commands.len() {
            self.execute_command();
        }
    }

    fn undo(&mut self) {
        if 0 == self.current_index {
            return;
        }

        self.current_index -= 1;

        let c = &self.commands[self.current_index];
        let t = &mut *self.target;
        c.undo(t);
    }
}


#[derive(Debug, Eq, PartialEq)]
struct Robot {
    x: i32,
    y: i32,
    dx: i32,
    dy: i32,
}


impl Robot {
    fn new() -> Robot {
        Robot {
            x: 0,
            y: 0,
            dx: 0,
            dy: 1,
        }
    }

    fn move_forward(&mut self) {
        self.x += self.dx;
        self.y += self.dy;
    }

    fn set_direction(&mut self, d: (i32, i32)) {
        self.dx = d.0;
        self.dy = d.1;
    }

    fn get_direction(&self) -> (i32, i32) {
        (self.dx, self.dy)
    }
}

enum RoboCommand {
    MoveForward,
    TurnRight,
    TurnLeft,
}

impl Command<Robot> for RoboCommand {
    fn execute(&self, r: &mut Robot) {
        use RoboCommand::*;
        match *self {
            MoveForward => r.move_forward(),
            TurnRight => {
                let (dx, dy) = r.get_direction();
                r.set_direction((dy, -dx))
            }
            TurnLeft => {
                let (dx, dy) = r.get_direction();
                r.set_direction((-dy, dx));
            }

        }
    }
    fn undo(&self, r: &mut Robot) {
        use RoboCommand::*;
        match *self {
            MoveForward => {
                let c1 = TurnRight;
                c1.execute(r);
                c1.execute(r);
                self.execute(r);
                c1.execute(r);
                c1.execute(r);
            }
            TurnRight => {
                let c = TurnLeft;
                c.execute(r);
            }
            TurnLeft => {
                let c = TurnRight;
                c.execute(r);

            }

        }
    }
}



fn main() {
    let mut r = Robot::new();

    let mut invoker = Invoker::new(&mut r);
    assert_eq!(*invoker.target(),
               Robot {
                   x: 0,
                   y: 0,
                   dx: 0,
                   dy: 1,
               });

    {
        use RoboCommand::*;
        invoker.append_command(TurnRight);
        invoker.append_command(TurnLeft);
        invoker.append_command(MoveForward);
    }

    invoker.execute_all_commands();
    assert_eq!(*invoker.target(),
               Robot {
                   x: 0,
                   y: 1,
                   dx: 0,
                   dy: 1,
               });

    invoker.undo();
    assert_eq!(*invoker.target(),
               Robot {
                   x: 0,
                   y: 0,
                   dx: 0,
                   dy: 1,
               });

    invoker.undo();
    assert_eq!(*invoker.target(),
               Robot {
                   x: 0,
                   y: 0,
                   dx: 1,
                   dy: 0,
               });
}

Stateパターン

参照先のままです。トレイトオブジェクトを使う代表的なケースだと思います。。 あるいは列挙型を使う可能性もあります。

Strategyパターン

参照先でも説明されていますが、Rustにはクロージャがあるので不要です。

Template Methodパターン

そもそもトレイトを使った普通のプログラミングなのでRustでわざわざ名前をつけるほどかな?と個人的には思いますがあえて書くなら参照先のままです。 あるいはものによっては高階関数でも。

個人的にはトレイトオブジェクトを作るより関連型を使った方が好みです。

trait AbstractFactory<'a> {
    type ProdX: ProductX;
    type ProdY: ProductY;
    fn create_product_x(&self) -> Box<ProdX + 'a>;
    fn create_product_y(&self) -> Box<ProdY + 'a>;
}

// ...

Mementoパターン

参照先のままです

Observerパターン

参照先のままです

Visitorパターン

参照先では簡単な例なので分かりづらいのですが、列挙型を使うところです。 まともな列挙型がない+シングルディスパッチしかなく引数はオーバーロードという二重苦によって生まれたパターンであり、まともな列挙型か多重ディスパッチがあれば複雑怪奇なプログラムは書かなくて済みます。

ここでは参照先とは違ってもう少し複雑な例を出します。

trait Visitor<T> {
    fn visit(&mut self, &T);
}

enum Entity {
    File(String),
    Dir(String, Vec<Entity>),
}

struct ConcreteFileVisitor;

impl Visitor<Entity> for ConcreteFileVisitor {
    fn visit(&mut self, e: &Entity) {
        use Entity::*;
        match *e {
            File(ref name) => println!("file: {}", name),
            Dir(ref name, ref files) => {
                println!("dir: {}", name);
                for file in files {
                    self.visit(file)
                }
            }
        }
    }
}

fn main() {
    use Entity::*;
    let e = Dir("/".to_string(),
                vec![File("etc".to_string()), File("usr".to_string())]);
    let mut visitor = ConcreteFileVisitor;
    visitor.visit(&e);
}

特段パターンというほどの処理をしている感じがしませんね。

Iteratorパターン

参照先のままです。

Mediatorパターン

だいたい参照先のままです。複雑なことをしようと思うとColleagueが複数種類出てきて列挙型かトレイトオブジェクトが必要になりそうな気がします。

Interpreterパターン

Builderパターン

あまりここまで抽象化してるのは見たことありませんがやるとしたら参照先のままです。 そもそもRustには継承がないので抽象化する意義があまりなく、型固有のBuilderパターンで十分です。

Prototypeパターン

参照先のままです。

Factoryパターン

クロージャで十分です。

trait Product {
    fn convert(&self, String) -> String;
}


struct Factory;

impl Factory {
    fn convert<P, F>(&self, s: String, create_product: F) -> String
        where P: Product,
              F: FnOnce() -> P
    {
        create_product().convert(s)
    }
}


struct ConcreteProductX;
impl Product for ConcreteProductX {
    fn convert(&self, s: String) -> String {
        s.to_uppercase()
    }
}


fn main() {
    let f = Factory;
    println!("{}",
             f.convert("hogehoge piyopiyo".to_string(), || ConcreteProductX))
}

AbstractFactoryパターン

そもそもここまでやる?というのは置いといてやるとしたら参照先のままかなぁと思います。

TemplateMethodパターンでも述べた通り個人的には関連型を使う方が好みです。

Chain of Responsibility/CoR パターン

参照先のままです。

Singletonパターン

そもそもアンチパターンです。分かりづらいグローバル変数なだけです。あえてRustでやるとしたらlazy_staticかなと思います。

Adapterパターン

これは捉え方が2種類あるかなーと思います。。

1つにはクラスの定義時にしかインターフェースを実装できない窮屈な言語仕様へのワークアラウンドとして。 この捉え方では参照先のコードようにただトレイトを実装してしまえば終わりです。

もう1つにはラッパーオブジェクトとして。std::fs::Fileの実装とかがそれっぽいと思います。。

pub struct File {
    inner: fs_imp::File,
}

Bridgeパターン

そもそも機能の追加とAPIの抽象化をどちらも継承に押し込める言語仕様が悪い。 それにRustでは継承しないので関係ないです。

Proxyパターン

参照先のままです。

Facadeパターン

参照先のままです。Rustにはモジュールや可視性の制御があって特別意識することなく普段からやっていることなのであまり名前をつけるほどのことではないと思ってます。

Flyweightパターン

所有権があるのでRustだとちょっと難しいパターンです。 参照だけなら参照先のようにHashMapにいれるか、オブジェクトを区別しないならVecにいれるかなと思います。

因みにLispとかではinternという名前で呼ばれてると思います。

Compositeパターン

ただの列挙型の再実装です。

Decoratorパターン

よくあるやつです。参照先のコードの他、std::io::WriteBufのようなものが代表的です。

おわりに

デザインパターンをdisろうと思って書いたのですが案外多くのケースで便利でした。Rustで不要なものは10本指で数えられる程度でしたね。すごい。

因みにLisperでAIの研究者(確か今GoogleのAI研究所の所長)のPeter NorvigはDesign Patterns in Dynamic Languagesで16個はLispの機能を使えばパターンという程のものではなくなると言ってます。 それぞれどの機能でどれが不要になるかを解説しているのですが、Rustは高階関数とモジュールの分に加えて列挙型の分で不要になってるかなと思います。

Written by κeen