Rust力を高めたいけれども、特に作るものが思い当たらなかったのでデザインパターンを真似して書いてみようと思った.
解釈間違い、表現間違い、などなど、コメントお願いします.
Githubにも同じコードを上げています
Github - mopp/CodeGarage/rust/design_patterns/
追記
κeenさんが大変良い記事を書いてくださいましたのでリンクを貼らせていただきます
Rust風にデザインパターン23種
以下の例では不十分な部分を補足、修正してくれています。
ありがとうございます。
結論
- 真似して書いてみました。が理解できたという実感が伴っていない
- おそらく格好のマサカリの的であろう
- 何かを作るときに実際の問題を考えながら使わなければ覚えられないし、体に馴染まない
- パターンがあるから覚えるのではなく、問題を解決したいからパターンを参照する、ではないと駄目だなと思った
- そしてなにより、これはRustの思想に反しているのではなかろうかという気持ちになった
振る舞いに関するパターン
Command パターン
いろいろな操作(コマンド)とそのパラメータをオブジェクトとすることで、拡張性を上げるパターン.
例えば、オブジェクトに自身を複合的な操作メソッドをたくさん生やしてしまうと、それ自身が大きくなり、メンテナンス性が下がる.
利点
- 新しい操作を簡単に追加できる.
- 元のオブジェクトを変更しないで、新しい操作のオブジェクトを追加するだけでいい.
- 小さい操作をたくさん組み合わせられる.
- それぞれの操作にundo機能をつければ、ロールバック的なことが容易に行える.
- それぞれの操作が自身の実行時間を持つことで、総実行時間の見積もりができる.
実装
-
Command
trait- 操作のインターフェースを定義
- 操作をオブジェクトにする抽象化をこいつで行う
- このtraitを実装したstructをconcrete commandと呼ぶ
-
Receiver
- 操作対象となる状態を持つクラス
-
Invoker
-
Command
を管理する
-
-
Client
- 色々と定義したものを使用するオブジェクト
- 以下の実装ではオブジェクトに包まずに、main関数の中に書いている
通常、このパターンを実装するときには、Command
のインスタンスに操作対象オブジェクトへの参照を渡すらしい.
しかし、Rustではpointer aliasingはunsafeなのでコンパイルエラーとなる.
なので、Invoker
に操作対象の参照を渡して、Command
実行毎に参照を渡している.
trait Command<T> {
fn execute(&self, &mut T);
fn undo(&self, &mut T);
}
struct Invoker<'a, T: 'a> {
commands: Vec<Box<Command<T> + 'a>>,
target: &'a mut T,
current_index: usize,
}
impl<'a, T> Invoker<'a, 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<U: Command<T> + 'a>(&mut self, c: U) {
self.commands.push(Box::new(c));
}
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)
}
}
struct CommandMoveForward;
impl Command<Robot> for CommandMoveForward {
fn execute(&self, r: &mut Robot) {
r.move_forward();
}
fn undo(&self, r: &mut Robot) {
let c1 = CommandTurnRight;
c1.execute(r);
c1.execute(r);
self.execute(r);
c1.execute(r);
c1.execute(r);
}
}
struct CommandTurnRight;
impl Command<Robot> for CommandTurnRight {
fn execute(&self, r: &mut Robot) {
let (dx, dy) = r.get_direction();
r.set_direction((dy, -dx));
}
fn undo(&self, r: &mut Robot) {
let c = CommandTurnLeft;
c.execute(r);
}
}
struct CommandTurnLeft;
impl Command<Robot> for CommandTurnLeft {
fn execute(&self, r: &mut Robot) {
let (dx, dy) = r.get_direction();
r.set_direction((-dy, dx));
}
fn undo(&self, r: &mut Robot) {
let c = CommandTurnRight;
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, });
invoker.append_command(CommandTurnRight);
invoker.append_command(CommandTurnLeft);
invoker.append_command(CommandMoveForward);
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 パターン
状態をオブジェクトで管理し、それを切り替えることで処理を変更可能とする.
設定された現在の状態に応じて、適切な処理に切り替えるという意味だと思う.
また、使う側が明示的に切り替えなくても、コンテキスト勝手に変更してもいいみたいだった.
利点
- 使うときに状態が何であるかを気にしなくて良い.
- 状態一つでクラス一つ、と実装を分けることができる.
実装
-
State
- とある状態を表すオブジェクトの処理用インターフェースを定義する
- このtraitを実装したstructをconcrete stateと呼ぶ
-
StateContext
- State毎に別れている処理をこのオブジェクトを用いて実行する
- このオブジェクトを経由して状態を変更する
-
Client
-
StateContext
を利用して処理を行う. - 利用者
-
順序付きの状態であるので、current_state
ではなくてnext_state
でも良かったかもしれない.
// 制御するボタンが一つだけついた電子サイコロ
// ボタンを押す、電源ON & サイコロ回転開始
// ボタンを押す、サイコロ停止(出目が決まる)
// ボタンを押す、電源OFF、最初に戻る.
use std::collections::HashMap;
#[derive(Debug, Eq, PartialEq, Hash)]
enum StateDice {
PowerOn,
StopDice,
PowerOff
}
trait State {
fn on_press_button(&self, &mut StateContext);
}
struct StatePowerOn;
impl State for StatePowerOn {
fn on_press_button(&self, context: &mut StateContext)
{
// Something to do for turning on the dice.
println!("Power on and Shake the dice.");
context.set_state(StateDice::StopDice);
}
}
struct StateStop;
impl State for StateStop {
fn on_press_button(&self, context: &mut StateContext)
{
// Something to do for turning on the dice.
println!("Stopping the dice.");
context.set_dice_number(4);
context.set_state(StateDice::PowerOff);
}
}
struct StatePowerOff;
impl State for StatePowerOff {
fn on_press_button(&self, context: &mut StateContext)
{
// Something to do for turning on the dice.
println!("Power off.");
context.set_state(StateDice::PowerOn);
}
}
#[derive(Debug)]
struct StateContext {
number: Option<u8>,
current_state: StateDice,
}
impl StateContext {
fn new() -> StateContext
{
StateContext {
number: None,
current_state: StateDice::PowerOn,
}
}
fn set_state(&mut self, s: StateDice)
{
self.current_state = s;
}
fn set_dice_number(&mut self, n :u8)
{
self.number = Some(n)
}
fn press_button<'a>(&mut self, hmap: &HashMap<StateDice, Box<State + 'a>>)
{
let b = hmap.get(&self.current_state).unwrap();
b.on_press_button(self);
}
}
fn main() {
let mut hmap = HashMap::new();
hmap.insert(StateDice::PowerOn, Box::new(StatePowerOn) as Box<State>);
hmap.insert(StateDice::StopDice, Box::new(StateStop) as Box<State>);
hmap.insert(StateDice::PowerOff, Box::new(StatePowerOff) as Box<State>);
let hmap = &hmap;
let mut context = StateContext::new();
context.press_button(hmap);
println!("{:?}", context);
context.press_button(hmap);
println!("{:?}", context);
context.press_button(hmap);
println!("{:?}", context);
}
Strategy パターン
アルゴリズムを実行時に選択可能なパターン.
アルゴリズムの集合を定義する方法を提供し、それらを交換可能にする.
関数が第一級オブジェクトである言語では、このパターンのための実装というものは不要である.
利点
- アルゴリズムを動的に切り替える必要がある場合に有用
- アルゴリズムを使用者から独立したまま変化させられる
実装
-
Strategy
- 処理のインターフェースを定義する
- このtraitを実装したstructをconcrete strategyと呼ぶ
- ここではBinaryFnにした
-
Context
- Strategyオブジェクトを保持、管理する
// 2つの引数を取り、何かしらの二項演算を行う.
type BinaryFn<T> = Fn(T, T) -> T;
struct Context<'a, T: 'a> {
strategy: &'a BinaryFn<T>,
}
impl<'a, T> Context<'a, T> {
fn new(f: &'a BinaryFn<T>) -> Context<'a, T>
{
Context {
strategy: f,
}
}
fn execute(&self, x: T, y: T) -> T
{
(*self.strategy)(x, y)
}
fn set_strategy(&mut self, f: &'a BinaryFn<T>)
{
self.strategy = f
}
}
fn main() {
let add = |x: usize, y: usize| x + y;
let mul = |x: usize, y: usize| x * y;
let div = |x: usize, y: usize| x / y;
let and = |x: usize, y: usize| x & y;
let mut c = Context::new(&add);
println!("{:?}", c.execute(1, 2));
c.set_strategy(&mul);
println!("{:?}", c.execute(1, 2));
c.set_strategy(&div);
println!("{:?}", c.execute(2, 2));
c.set_strategy(&and);
println!("{:?}", c.execute(2, 2));
}
Template Method パターン
処理の流れと具体的な実装を分けることで汎用性を高める.
利点
- 流れは同じだけど、細部が違う、という実装が容易
- 拡張性も高まる
実装
-
AbstractClass
- 処理の流れのみ定義する.
- 具体的な処理をconcrete classとして実装する.
- (rustでclassっていうのどうなんだろう?)
trait AbstractFactory<'a> {
fn create_product_x(&self) -> Box<ProductX + 'a>;
fn create_product_y(&self) -> Box<ProductY + 'a>;
}
trait ProductX {
fn get_value(&self) -> String;
}
trait ProductY {
fn get_value(&self) -> String;
}
struct ConcreteProductX(String);
impl ConcreteProductX {
fn new(msg: String) -> ConcreteProductX {
ConcreteProductX(msg + &" ProductX".to_string())
}
}
impl ProductX for ConcreteProductX {
fn get_value(&self) -> String
{
self.0.clone()
}
}
struct ConcreteProductY(String);
impl ConcreteProductY {
fn new(msg: String) -> ConcreteProductY {
ConcreteProductY(msg + &" ProductY".to_string())
}
}
impl ProductY for ConcreteProductY {
fn get_value(&self) -> String
{
self.0.clone()
}
}
struct ConcreteFactoryA;
impl<'a> AbstractFactory<'a> for ConcreteFactoryA {
fn create_product_x(&self) -> Box<ProductX + 'a>
{
Box::new(ConcreteProductX::new("FactoryA".to_string())) as Box<ProductX>
}
fn create_product_y(&self) -> Box<ProductY + 'a>
{
Box::new(ConcreteProductY::new("FactoryA".to_string())) as Box<ProductY>
}
}
struct ConcreteFactoryB;
impl<'a> AbstractFactory<'a> for ConcreteFactoryB {
fn create_product_x(&self) -> Box<ProductX + 'a>
{
Box::new(ConcreteProductX::new("FactoryB".to_string())) as Box<ProductX>
}
fn create_product_y(&self) -> Box<ProductY + 'a>
{
Box::new(ConcreteProductY::new("FactoryB".to_string())) as Box<ProductY>
}
}
enum FactoryID {
A,
B,
}
fn create_factory<'a>(id: FactoryID) -> Box<AbstractFactory<'a> + 'a>
{
match id {
FactoryID::A => Box::new(ConcreteFactoryA),
FactoryID::B => Box::new(ConcreteFactoryB),
}
}
fn main()
{
let factory_a = create_factory(FactoryID::A);
let a_x = factory_a.create_product_x();
let a_y = factory_a.create_product_y();
println!("{}", a_x.get_value());
println!("{}", a_y.get_value());
let factory_b = create_factory(FactoryID::B);
let b_x = factory_b.create_product_x();
let b_y = factory_b.create_product_y();
println!("{}", b_x.get_value());
println!("{}", b_y.get_value());
}
Memento パターン
オブジェクトの状態を記憶して、undoをできるようにする.
利点
- Undo/ロールバックできるようになる
実装
-
Originator
- 内部状態を持つオブジェクト
-
Caretaker
-
Memento
を保存しておく
-
-
Memento
-
Originator
の状態を記憶するオブジェクト
-
trait Originator {
fn generate_memento(&self) -> Box<Memento>;
fn restore_from_memento(&mut self, &Memento);
}
trait Caretaker {
fn add_memento(&mut self, Box<Memento>);
fn get_memento(&mut self, usize) -> &Memento;
}
trait Memento {
fn get_value(&self) -> usize;
}
#[derive(Debug)]
struct OriginatorX(usize);
impl Originator for OriginatorX {
fn generate_memento(&self) -> Box<Memento>
{
Box::new(MementoX(self.0))
}
fn restore_from_memento(&mut self, m: &Memento)
{
self.0 = m.get_value()
}
}
struct MementoX(usize);
impl Memento for MementoX {
fn get_value(&self) -> usize
{
self.0
}
}
struct CaretakerX {
history: Vec<Box<Memento>>,
}
impl CaretakerX {
fn new() -> CaretakerX {
CaretakerX {
history: Vec::new(),
}
}
}
impl Caretaker for CaretakerX {
fn add_memento(&mut self, m: Box<Memento>)
{
self.history.push(m)
}
fn get_memento(&mut self, index: usize) -> &Memento
{
& *self.history[index]
}
}
fn main()
{
let mut caretaker = CaretakerX::new();
let mut originator = OriginatorX(10);
caretaker.add_memento(originator.generate_memento());
println!("{:?}", originator);
originator.0 = 99;
println!("{:?}", originator);
originator.restore_from_memento(caretaker.get_memento(0));
println!("{:?}", originator);
}
Observer パターン
一番良く見かけるパターン
イベント処理を書くときの定石だと思う.
利点
- イベント駆動の実現
実装
-
Observer
- 観察をするオブジェクト
- イベント発生時に呼び出される
- 呼び出し順序的に逆なので混乱しないように注意
-
Subject
- 複数の
Observer
を管理し、イベントを受取、伝播させる
- 複数の
trait Subject<T: Clone> {
fn notify_observers(&self, &T);
fn register_observer(&mut self, Box<Observer<T>>) -> usize;
fn unregister_observer(&mut self, usize);
}
trait Observer<T: Clone> {
fn on_notify(&self, &T);
}
#[derive(Debug, Clone)]
struct EventObject(usize);
struct SubjectX {
observers: Vec<(bool, Box<Observer<EventObject>>)>,
}
impl SubjectX {
fn new() -> SubjectX
{
SubjectX {
observers: Vec::new(),
}
}
}
impl Subject<EventObject> for SubjectX {
fn notify_observers(&self, e: &EventObject)
{
for observer in self.observers.iter() {
if observer.0 {
observer.1.on_notify(e);
}
}
}
fn register_observer(&mut self, o: Box<Observer<EventObject>>) -> usize
{
self.observers.push((true, o));
self.observers.len() - 1
}
fn unregister_observer(&mut self, i: usize)
{
self.observers[i].0 = false
}
}
struct ObserverX(usize);
impl Observer<EventObject> for ObserverX {
fn on_notify(&self, e: &EventObject)
{
println!("ObserverX {} Get {:?}", self.0, e);
}
}
fn main()
{
let mut subject = SubjectX::new();
subject.register_observer(Box::new(ObserverX(1)));
subject.register_observer(Box::new(ObserverX(2)));
subject.register_observer(Box::new(ObserverX(3)));
subject.notify_observers(&EventObject(100));
subject.notify_observers(&EventObject(20));
}
Visitor パターン
データ構造とそれに対する処理を分離する.
利点
- 処理を持つオブジェクト、データを持つオブジェクトを分けてかける
実装
-
Visitor
- 訪問をするオブジェクト
- 処理を持つ
-
Accepter
- 訪問者を受け入れるオブジェクト
- データを持つ
trait Visitor {
fn visit(&self, &Acceptor);
}
trait Acceptor {
fn accept(&self, &Visitor);
fn get_value(&self) -> &String;
}
struct VisitorX;
impl Visitor for VisitorX {
fn visit(&self, a: &Acceptor)
{
println!("VisitorX - Acceptor {}", a.get_value());
}
}
struct AcceptorX(String);
impl Acceptor for AcceptorX {
fn accept(&self, v: &Visitor)
{
v.visit(self);
}
fn get_value(&self) -> &String
{
&self.0
}
}
fn main()
{
let v = VisitorX;
let a1 = AcceptorX("Number 1".to_string());
let a2 = AcceptorX("Number 2".to_string());
a1.accept(&v);
a2.accept(&v);
}
Iterator パターン
Observerパターン並によく見る.
データに寄らない反復子を作るためのパターン.
利点
- コンテナオブジェクトを作り、イテレータのインターフェースを実装すれば、簡単に反復子が得られる
実装
普通にRustのIteratorを実装しただけ.
struct Container<T: Sized + Copy> {
buf: Vec<T>,
index: usize,
}
impl<T: Copy> Container<T> {
fn new() -> Container<T>
{
Container {
buf: Vec::new(),
index: 0,
}
}
fn add(&mut self, t: T)
{
self.buf.push(t);
}
}
impl<T: Copy> Iterator for Container<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item>
{
match self.index < self.buf.len() {
true => {
let t = Some(self.buf[self.index]);
self.index += 1;
t
}
false => None,
}
}
}
fn main()
{
let mut c: Container<usize> = Container::<usize>::new();
c.add(10);
c.add(20);
c.add(30);
for i in c {
println!("{}", i);
}
}
Mediator パターン
Mediatorという仲介者を経由して処理を行うようにする.
オブジェクト間のやり取りが複雑にならないように仲介を経由する.
利点
- オブジェクト同士の依存性が減る
実装
-
Mediator
-
Colleague
を受け取って管理 -
Colleague
からの要求をどこに流すかを制御する
-
-
Colleague
-
Mediator
に登録される - 何か要求がある場合は
Mediator
へ要求する
-
use std::collections::HashMap;
struct Mediator {
colleagues: HashMap<String, Colleague>,
}
impl Mediator {
fn new() -> Mediator {
Mediator {
colleagues: HashMap::new(),
}
}
fn add_colleague(&mut self, c: Colleague)
{
self.colleagues.insert(c.0.clone(), c);
}
fn consult_to(&self, s: &String, msg: String)
{
self.colleagues.get(s).unwrap().receive_msg(msg);
}
fn get(&self, s: &String) -> &Colleague
{
self.colleagues.get(s).unwrap()
}
}
struct Colleague(String);
impl Colleague {
fn new(s: &String) -> Colleague
{
Colleague(s.clone())
}
fn send_msg(&self, m: &Mediator, to: &String, msg: String)
{
m.consult_to(to, msg);
}
fn receive_msg(&self, msg: String)
{
println!("{} gets {}", self.0, msg);
}
}
fn main()
{
let mut mediator = Mediator::new();
let key1 = "Hoge".to_string();
let c1 = Colleague::new(&key1);
let key2 = "Piyo".to_string();
let c2 = Colleague::new(&key2);
mediator.add_colleague(c1);
mediator.add_colleague(c2);
let c1 = mediator.get(&key1);
c1.send_msg(&mediator, &key2, "hi from Hoge".to_string());
let c2 = mediator.get(&key2);
c2.send_msg(&mediator, &key1, "hi from Piyo".to_string());
}
Interpreter パターン
DSLを作ろうなっていうパターン.
例としては、SQLが挙げられるらしい.
利点
- DSLの実行環境を実装することで、そのままプログラミングするよりも高速に処理できるときがあるらしい (数倍から数百倍)
実装
struct Interpreter;
impl Interpreter {
fn parse_and_execute(s: String)
{
let mut s = s.clone();
if let Some(i) = s.find(' ') {
let word = s.split_off(i);
let times = s.parse::<usize>().unwrap();
for _ in 0..times {
print!("{} ", word);
}
println!("");
}
}
}
fn main()
{
Interpreter::parse_and_execute("10 hey guys !".to_string());
}
生成に関するパターン
Builder パターン
生成されるオブジェクトと生成するためのロジックが分離される.
利点
- 生成の処理だけを分離しているので、具体的な値はまた設定できる
- 例えば、よく使う値の組み合わせをBuilderオブジェクトにしておくとか
実装
-
Builder
- 生成ロジックを持つ
-
Director
-
Builder
を使ってオブジェクトを生成する
-
-
Client
-
Builder
とDirector
を組み合わせて使用する
-
#[derive(Clone, Debug)]
struct Person {
name: String,
age: u8,
job: Option<String>,
}
impl Person {
fn new() -> Person
{
Person {
name: Default::default(),
age: Default::default(),
job: None,
}
}
fn set_name(&mut self, name: String)
{
self.name = name
}
fn set_age(&mut self, age: u8)
{
self.age = age
}
fn set_job(&mut self, job: Option<String>)
{
self.job = job;
}
}
trait Builder {
fn build_name(&mut self);
fn build_age(&mut self);
fn build_job(&mut self);
fn build(&mut self) -> Person;
}
struct AliceBuilder {
obj: Person,
}
impl AliceBuilder {
fn new() -> AliceBuilder
{
AliceBuilder {
obj: Person::new(),
}
}
}
impl Builder for AliceBuilder {
fn build_name(&mut self)
{
self.obj.set_name("Alice".to_string())
}
fn build_age(&mut self)
{
self.obj.set_age(12)
}
fn build_job(&mut self)
{
self.obj.set_job(Some("Student".to_string()))
}
fn build(&mut self) -> Person
{
self.obj.clone()
}
}
struct Director {
builder: Box<Builder>,
}
impl Director {
fn new(b: Box<Builder>) -> Director
{
Director {
builder: b,
}
}
fn build(&mut self) -> Person
{
self.builder.build_name();
self.builder.build_age();
self.builder.build_job();
self.builder.build()
}
}
fn main() {
let alice_builder = Box::new(AliceBuilder::new()) as Box<Builder>;
let mut director = Director::new(alice_builder);
let alice = director.build();
println!("{:?}", alice);
}
Prototype パターン
プロトタイプを一つ作り、それをcloneして別のインスタンスを作成する.
利点
- 生成コストが大きいものをコピーして使いまわす.
実装
-
Prototype
- 値を設定するインターフェースを定義.
- Clone可能にしておく(rustはtrait束縛と、deriveがあって便利だなと思った)
trait Prototype: Clone {
fn set_x(&mut self, usize);
fn set_y(&mut self, usize);
}
#[derive(Debug, Clone)]
struct Object {
x: usize,
y: usize,
}
impl Object {
fn new() -> Object
{
Object {
x: 100,
y: 200,
}
}
}
impl Prototype for Object {
fn set_x(&mut self, x: usize)
{
self.x = x;
}
fn set_y(&mut self, y: usize)
{
self.y = y;
}
}
fn main()
{
let origin = Object::new();
let mut obj = origin.clone();
obj.set_x(123);
println!("origin = {:?}", origin);
println!("obj = {:?}", obj);
}
Factory Method パターン
使用するオブジェクトの生成を外側に出す.
利点
- インターフェースを統一し、生成部分を外側に出すことで、処理の切り替えが容易になる
実装
-
Product
- 生成されるオブジェクトのインターフェースを定義する
-
Factory
- オブジェクトを生成する
- ここで使用してもよいのだろうか?
trait Product {
fn convert(&self, String) -> String;
}
trait Factory {
fn create_product(&self) -> Box<Product>;
fn convert(&self, s: String) -> String
{
self.create_product().convert(s)
}
}
struct ConcreteProductX;
impl Product for ConcreteProductX {
fn convert(&self, s: String) -> String
{
s.to_uppercase()
}
}
struct ConcreteFactoryX;
impl Factory for ConcreteFactoryX {
fn create_product(&self) -> Box<Product>
{
Box::new(ConcreteProductX) as Box<Product>
}
}
fn main()
{
let f = ConcreteFactoryX;
println!("{}", f.convert("hogehoge piyopiyo".to_string()))
}
Abstract Factory パターン
関係のあるオブジェクトをまとめて生成する方法を提供するためのパターン.
利点
- Factoryを切り替えることで生成されるオブジェクトをがらっと変えられる.
実装
-
AbstractFactory
- 生成に必要なインターフェースを定義する.
-
Product
- 生成されるオブジェクトのインターフェースを定義する.
trait AbstractFactory<'a> {
fn create_product_x(&self) -> Box<ProductX + 'a>;
fn create_product_y(&self) -> Box<ProductY + 'a>;
}
trait ProductX {
fn get_value(&self) -> String;
}
trait ProductY {
fn get_value(&self) -> String;
}
struct ConcreteProductX(String);
impl ConcreteProductX {
fn new(msg: String) -> ConcreteProductX {
ConcreteProductX(msg + &" ProductX".to_string())
}
}
impl ProductX for ConcreteProductX {
fn get_value(&self) -> String
{
self.0.clone()
}
}
struct ConcreteProductY(String);
impl ConcreteProductY {
fn new(msg: String) -> ConcreteProductY {
ConcreteProductY(msg + &" ProductY".to_string())
}
}
impl ProductY for ConcreteProductY {
fn get_value(&self) -> String
{
self.0.clone()
}
}
struct ConcreteFactoryA;
impl<'a> AbstractFactory<'a> for ConcreteFactoryA {
fn create_product_x(&self) -> Box<ProductX + 'a>
{
Box::new(ConcreteProductX::new("FactoryA".to_string())) as Box<ProductX>
}
fn create_product_y(&self) -> Box<ProductY + 'a>
{
Box::new(ConcreteProductY::new("FactoryA".to_string())) as Box<ProductY>
}
}
struct ConcreteFactoryB;
impl<'a> AbstractFactory<'a> for ConcreteFactoryB {
fn create_product_x(&self) -> Box<ProductX + 'a>
{
Box::new(ConcreteProductX::new("FactoryB".to_string())) as Box<ProductX>
}
fn create_product_y(&self) -> Box<ProductY + 'a>
{
Box::new(ConcreteProductY::new("FactoryB".to_string())) as Box<ProductY>
}
}
enum FactoryID {
A,
B,
}
fn create_factory<'a>(id: FactoryID) -> Box<AbstractFactory<'a> + 'a>
{
match id {
FactoryID::A => Box::new(ConcreteFactoryA),
FactoryID::B => Box::new(ConcreteFactoryB),
}
}
fn main()
{
let factory_a = create_factory(FactoryID::A);
let a_x = factory_a.create_product_x();
let a_y = factory_a.create_product_y();
println!("{}", a_x.get_value());
println!("{}", a_y.get_value());
let factory_b = create_factory(FactoryID::B);
let b_x = factory_b.create_product_x();
let b_y = factory_b.create_product_y();
println!("{}", b_x.get_value());
println!("{}", b_y.get_value());
}
Chain of Responsibility/CoR パターン
処理対象をたらい回しにする.
ifによる条件分岐内の処理がオブジェクトになりチェーンするような感じ.
利点
- 自分の行う処理に集中してオブジェクトとなる
- コードが疎結合になる
実装
-
Cor
- 処理対象の受取口を定義する
-
Request
- 処理対象のインターフェースを定義する
trait Cor {
fn process_request(&self, &mut Request);
}
trait Request {
fn get_level(&self) -> Level;
fn get_something(&self) -> usize;
}
struct RequestX {
level: Level,
v: usize,
}
impl RequestX {
fn new(l: Level, v: usize) -> RequestX
{
RequestX {
level: l,
v: v,
}
}
}
impl Request for RequestX {
fn get_level(&self) -> Level
{
self.level
}
fn get_something(&self) -> usize
{
self.v
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Level {
High,
Middle,
Low,
}
struct ImplCor {
next: Option<Box<Cor>>,
allowable_level: Level,
}
impl ImplCor {
fn new(l: Level, next: Option<Box<Cor>>) -> ImplCor {
ImplCor {
next: next,
allowable_level: l,
}
}
}
impl Cor for ImplCor {
fn process_request(&self, r: &mut Request)
{
print!("{:?}: ", self.allowable_level);
if self.allowable_level == r.get_level() {
println!("Request accepted - v = {}", r.get_something());
} else {
if let Some(ref next) = self.next {
println!("Pass to the next");
next.process_request(r);
} else {
println!("Chain finished.");
}
}
}
}
fn main() {
let high = ImplCor::new(Level::High, None);
let middle = ImplCor::new(Level::Middle, Some(Box::new(high)));
let low = ImplCor::new(Level::Low, Some(Box::new(middle)));
let mut r1 = RequestX::new(Level::High, 1);
let mut r2 = RequestX::new(Level::Middle, 2);
let mut r3 = RequestX::new(Level::Low, 3);
low.process_request(&mut r3);
low.process_request(&mut r2);
low.process_request(&mut r1);
}
Singleton パターン
ただ一つのオブジェクトだけを生成するためのパターン
利点
- 複数生成されては困るオブジェクトに対して有効
実装
unsafeとグローバル変数を使ってしまった.
これ、Rustでやっちゃいけないんじゃないだろうか?
static mut SINGLETON_G: Option<Singleton> = None;
#[derive(Debug)]
struct Singleton {
v: usize,
}
impl Singleton {
fn new() -> &'static mut Singleton
{
unsafe {
match SINGLETON_G {
Some(ref mut obj) => obj,
None => {
SINGLETON_G = Some(Singleton{v: 100});
Singleton::new()
}
}
}
}
}
fn main()
{
let s1 = Singleton::new();
let s2 = Singleton::new();
println!("{:?}", s1);
println!("{:?}", s2);
s1.v = 999;
println!("{:?}", s1);
println!("{:?}", s2);
}
構造に関するパターン
Adapter パターン
AndroidのList表示関係で使った記憶がある.
ACアダプターのアダプターと同じような意味で、構造を別の構造に変換する役割を持つ.
利点
- インターフェースなどが異なるオブジェクト間にて、双方を変更することなく組み合わせることができる.
実装
-
Adapter
- 変換の橋渡しをする
- Rustで書いたときtrait束縛したいなと思ったので、普通にtraitでインターフェースを定義したみたいになってしまった
trait Adapter {
fn get_a(&self) -> usize;
fn get_b(&self) -> usize;
}
struct ObjectX {
a: usize,
b: usize,
}
impl Adapter for ObjectX {
fn get_a(&self) -> usize
{
self.a
}
fn get_b(&self) -> usize
{
self.b
}
}
struct ObjectY {
m: u8,
n: u8,
}
impl Adapter for ObjectY {
fn get_a(&self) -> usize
{
self.m as usize
}
fn get_b(&self) -> usize
{
self.n as usize
}
}
fn main()
{
let obj_x = ObjectX {a: 10, b: 120};
let obj_y = ObjectY {m: 1, n: 2};
use_via_adapter(&obj_x);
use_via_adapter(&obj_y);
}
fn use_via_adapter(adapter: &Adapter)
{
println!("a = {}, b = {}", adapter.get_a(), adapter.get_b());
}
Bridge パターン
機能の追加と、機能の実装を分離し、それを橋渡しするというパターン.
継承が発生しすぎてやばい!というコードに対して有効.
利点
- 拡張性の向上
- 継承による煩雑さの解消
実装
-
Abstraction
-
Implementor
を持ち、処理を制御する
-
-
RefinedAbstraction
-
Abstraction
に機能を追加したもの
-
-
Implementor
-
Abstraction
が使用する機能のインターフェースを定義する
-
trait Implementor {
fn decorate(&self, String) -> String;
}
struct ParenImpl;
impl Implementor for ParenImpl {
fn decorate(&self, msg: String) -> String
{
"(".to_string() + &msg + &")".to_string()
}
}
struct BracketImpl;
impl Implementor for BracketImpl {
fn decorate(&self, msg: String) -> String
{
"{".to_string() + &msg + &"}".to_string()
}
}
struct Abstraction<'a> {
implementer: &'a Implementor
}
impl<'a> Abstraction<'a> {
fn new(i: &Implementor) -> Abstraction {
Abstraction {
implementer: i,
}
}
fn convert(&self, msg: String) -> String
{
self.implementer.decorate(msg)
}
}
struct RefinedAbstraction<'a> {
abstraction: Abstraction<'a>,
}
impl<'a> RefinedAbstraction<'a> {
fn new(i: &Implementor) -> RefinedAbstraction {
RefinedAbstraction {
abstraction: Abstraction::new(i)
}
}
fn convert(&self, msg: String) -> String
{
self.abstraction.convert(msg)
}
fn print_convert_msg(&self, msg: String)
{
println!("{}", self.abstraction.convert(msg));
}
}
fn main()
{
let paren_impl = &ParenImpl;
let bracket_impl = &BracketImpl;
let abst_p = RefinedAbstraction::new(paren_impl as &Implementor);
let abst_b = RefinedAbstraction::new(bracket_impl as &Implementor);
println!("{}", abst_p.convert("YOYO".to_string()));
abst_b.print_convert_msg("oops".to_string());
}
Proxy パターン
別のもの(代理人)としてのインターフェースを提供するためのパターン.
利点
- コストが高いものの処理遅延などができる.
実装
-
Subject
- 操作用のインターフェースを定義する
-
RealSubject
- 実際の高コストなものや、包み込みたいもの
-
Proxy
-
Subject
のインターフェースを実装し、RealSubject
の代わりに振る舞うもの.
-
この実装ではProxy
に包んで、使用されるときに初めて生成されるようにした.
RealSubject
の方で使用時まで遅延させればいいじゃないか、とおもうのは当然だけど、それがライブラリなど、自分の管理外に合ったとき(変更不可のとき)
このパターンで遅延できるなと思う.
trait Subject {
fn get_something(&mut self) -> usize;
}
struct RealSubject(usize);
impl RealSubject {
fn new() -> RealSubject
{
let mut rs = RealSubject(0);
rs.load_something();
rs
}
fn load_something(&mut self)
{
println!("Try to load something, it is extremely heavy.");
self.0 = 100;
}
}
impl Subject for RealSubject {
fn get_something(&mut self) -> usize
{
self.0
}
}
struct Proxy(Option<RealSubject>);
impl Proxy {
fn new() -> Proxy
{
Proxy(None)
}
}
impl Subject for Proxy {
fn get_something(&mut self) -> usize
{
match self.0 {
Some(ref mut something) => {
something.get_something()
},
None => {
let mut rs = RealSubject::new();
let x = rs.get_something();
self.0 = Some(rs);
x
}
}
}
}
fn main()
{
let mut rs = RealSubject::new();
println!("Create RealSubject");
println!("{}", rs.get_something());
let mut p1 = Proxy::new();
println!("Create Proxy Object");
let mut p2 = Proxy::new();
println!("Create Proxy Object");
println!("{}", p1.get_something());
println!("{}", p2.get_something());
}
Facade パターン
異なるサブシステムを単純な操作だけのFacadeクラスで結ぶ.
複雑な内部処理を隠蔽し、簡素なインターフェースを使ってもらう.
利点
- 複雑度が下がる
- 具体的な処理を持つオブジェクトは利用されるということを気にしなくていいので、自分の処理に集中できる.
- 素朴
実装
fn worker_a()
{
println!("hoge");
}
fn worker_b()
{
println!("huga");
}
fn worker_c()
{
println!("piyo");
}
struct Facade;
impl Facade {
fn facade_method(&self)
{
worker_a();
worker_b();
worker_c();
}
}
fn main()
{
let f = Facade;
f.facade_method();
}
Fryweight パターン
いわゆる、オブジェクトのキャッシュ
利点
- メモリ使用量の削減
実装
use std::collections::HashMap;
#[derive(Debug)]
struct Object(String, usize);
struct Fryweight {
pool: HashMap<String, Object>,
counter: usize,
}
impl Fryweight {
fn new() -> Fryweight
{
Fryweight {
pool: HashMap::new(),
counter: 0,
}
}
fn obtain_object(&mut self, key: String) -> &mut Object
{
if self.pool.contains_key(&key) {
return self.pool.get_mut(&key).unwrap()
}
self.pool.insert(key.clone(), Object(key.clone(), self.counter));
self.counter += 1;
self.obtain_object(key)
}
}
fn main()
{
let mut fryweight = Fryweight::new();
{
let o1 = fryweight.obtain_object("hoge".to_string());
println!("{:?}", o1);
o1.1 = 567;
}
let o2 = fryweight.obtain_object("hoge".to_string());
println!("{:?}", o2);
}
Composite パターン
木構造を伴う再帰的データ構造(ファイル構造がよく例として挙げられる)を表現するのに有効なパターン
共通のインターフェースを定義して、それぞれの具体的なデータ構造に実装させ、同じように扱う.
利点
- 再帰的な構造の表現が簡単になる
- 共通インターフェースを設けるので、処理において、データ構造ごとの分岐が不要
実装
-
Component
- 共通インターフェースを定義する
-
Composite
-
Component
を実装したオブジェクトに対する処理を持つ
-
-
Leaf
- データ構造体
trait Composite {
fn get_name(&self) -> String;
fn get_child(&self) -> Option<&Box<Composite>>;
fn set_child(&mut self, Box<Composite>);
fn print_child_name_recursive(&self);
}
struct File {
name: String,
child: Option<Box<Composite>>,
}
impl File {
fn new(name: String) -> File
{
File {
name: name,
child: None,
}
}
}
impl Composite for File {
fn get_name(&self) -> String
{
self.name.clone()
}
fn get_child(&self) -> Option<&Box<Composite>>
{
match self.child {
Some(ref x) => Some(x),
None => None,
}
}
fn set_child(&mut self, c: Box<Composite>)
{
self.child = Some(c)
}
fn print_child_name_recursive(&self)
{
print!(" -> {}", self.get_name());
if let Some(x) = self.get_child() {
x.print_child_name_recursive();
} else {
println!("");
}
}
}
// A directory is a file.
struct Directory {
f: File,
}
impl Directory {
fn new(name: String) -> Directory
{
Directory {
f: File::new(name),
}
}
}
impl Composite for Directory {
fn get_name(&self) -> String
{
self.f.get_name()
}
fn get_child(&self) -> Option<&Box<Composite>>
{
self.f.get_child()
}
fn set_child(&mut self, c: Box<Composite>)
{
self.f.set_child(c)
}
fn print_child_name_recursive(&self)
{
self.f.print_child_name_recursive();
}
}
fn main()
{
let mut d1 = Directory::new("root".to_string());
let mut d2 = Directory::new("boot".to_string());
let f1 = File::new("vmlinuz-linux".to_string());
d2.set_child(Box::new(f1));
d1.set_child(Box::new(d2));
println!("Start");
d1.print_child_name_recursive();
}
Decorator パターン
既存のオブジェクトに新しい処理を追加することを可能にする.
利点
- 今まで作ってきたものに変更を加えず、新しいものを追加できる
- 追加というよりラッピングするという感じなのでdecoratorというのだと思った
実装
-
Component
- 最低限実装していなければならない共通のインターフェース
-
Decorator
- 拡張するためのインターフェース
trait Component {
fn do_something(&self);
}
trait Decorator: Component {
fn do_something_more(&self);
}
struct BaseObject(usize);
impl Component for BaseObject {
fn do_something(&self)
{
println!("something: {}", self.0);
}
}
struct DecoratedObject {
base: BaseObject,
more_value: usize,
}
impl Component for DecoratedObject {
fn do_something(&self)
{
self.base.do_something()
}
}
impl Decorator for DecoratedObject {
fn do_something_more(&self)
{
println!("something more: {}", self.more_value);
}
}
fn process(c: &Component)
{
c.do_something()
}
fn main()
{
let obj = BaseObject(100);
process(&obj);
let dobj = DecoratedObject {
base: obj,
more_value: 999,
};
process(&dobj);
dobj.do_something_more();
}
参考
- ちびキャラでおぼえるデザインパターン でざぱたん ver.1.0.1
- IT専科 - デザインパターン入門
- Qiita - デザインパターン一覧 23種類
-
Wikipedia - デザインパターン
- 英語版の方も要参照