以前勉強しかけたデザインパターンを改めて学んでいるため、最近使っているDartでサンプルを書きながら説明していきたいと思います。
今回はStrategy Patternです。
Strategy Patternってどんなの?
Strategy Patternとは、振る舞いに対してコンポジションを使うパターンのことです。
サンプルを見てみましょう。下はそれぞれ、犬が吠えるための bark
メソッドを、それぞれ継承・Strategy Patternで書いたものです。
継承
abstract class Dog {
void bark();
}
class ToyPoodle extends Dog {
@override
void bark() => print('ワン!');
}
class Bulldog extends Dog {
@override
void bark() => print('バウ!');
}
Strategy Pattern
abstract class Dog {
const Dog({required this.bark});
final BarkBehavior bark;
}
class ToyPoodle extends Dog {
ToyPoodle() : super(bark: TinyBarkBehavior);
}
class Bulldog extends Dog {
Bulldog() : super(bark: StrongBarkBehavior);
}
typedef BarkBehavior = void Function();
final StrongBarkBehavior = () => print('バウ!');
final TinyBarkBehavior = () => print('ワン!');
何が嬉しいの?
パッと見た限り、Strategy Patternのほうが煩雑に見えるかもしれません。ただ、変更には圧倒的にStrategy Patternのほうが強いです。
具体的なメリットとしては下記があげられます。
- 特定の犬どうしで振る舞いを共通化できる
- 機能追加で副作用を与えない
それぞれ説明させていただきます。
特定の犬どうしで振る舞いを共通化できる
新しい犬 ドーベルマン
を追加することになりました。それぞれ対応してみましょう。
継承
class Doberman extends Dog {
@override
void bark() => print('バウ!');
}
Strategy Pattern
class Doberman extends Dog {
Doberman() : super(bark: StrongBarkBehavior);
}
いかがでしょう?
継承を使ったパターンだと、ブルドックと同じ鳴き方をする(諸説ある)ドーベルマンにたいして、同じ処理を繰り返し書いてしまっています。これでは保守性が悪くなってしまいますね。
機能追加で副作用を与えない
犬は走るでしょ!という要望があり、機能の追加が入りました。それぞれ対応してみましょう。
継承
abstract class Dog {
void bark();
void run() => print('犬が走っています');
}
Strategy Pattern
abstract class Dog {
const Dog({
required this.bark,
required this.run,
});
final BarkBehavior bark;
final RunBehavior run;
}
class ToyPoodle extends Dog {
ToyPoodle()
: super(
bark: TinyBarkBehavior,
run: NormalRunBehavior,
);
}
class Bulldog extends Dog {
Bulldog()
: super(
bark: StrongBarkBehavior,
run: NormalRunBehavior,
);
}
typedef RunBehavior = void Function();
final NormalRunBehavior = () => print('犬が走っています');
はい。完了です。
あれ?Strategy Patternは改修多くない?と思われたかもしれません。
ですがこのあと継承をつかったパターンではバグが発生しました。 以前追加した、走ることのできないおもちゃのトイプードルのことを忘れていた
のです。
↑には書いていませんでしたが、StrategyPatternを使っていたパターンではこれに気づき、対応できていました。
class ToyToyPoodle extends Dog {
ToyToyPoodle()
: super(
bark: TinyBarkBehavior,
run: CanNotRunBehavior,
);
}
final CanNotRunBehavior = () => print('この犬は走れません');
まとめ
いかがだったでしょうか?Strategy Pattern、かなり便利そうですね。
今後も頑張って書きます。LGTMやTwitterで感想・フォローいただけるともっとがんばれます。
参考文献から変更した部分
- Behavior毎にクラス用意されてましたが、Dartには
typedef
があるのでそちらを使うほうが良いと判断し変更しました。 - 参考文献ではBehavior毎にクラス分けされていたため、Behaviorの関数を呼び出すための関数が別途用意されていましたが、そもそもクロージャで渡すことにしたので用意していません。