2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Dartでデザインパターン-Strategy Pattern編

Last updated at Posted at 2022-03-26

以前勉強しかけたデザインパターンを改めて学んでいるため、最近使っているDartでサンプルを書きながら説明していきたいと思います。
今回はStrategy Patternです。

Strategy Patternってどんなの?

Strategy Patternとは、振る舞いに対してコンポジションを使うパターンのことです。
サンプルを見てみましょう。下はそれぞれ、犬が吠えるための bark メソッドを、それぞれ継承・Strategy Patternで書いたものです。

継承

dog.dart
abstract class Dog {
  void bark();
}
toy_poodle.dart
class ToyPoodle extends Dog {
  @override
  void bark() => print('ワン!');
}
bulldog.dart
class Bulldog extends Dog {
  @override
  void bark() => print('バウ!');
}

Strategy Pattern

dog.dart
abstract class Dog {
  const Dog({required this.bark});

  final BarkBehavior bark;
}
toy_poodle.dart
class ToyPoodle extends Dog {
  ToyPoodle() : super(bark: TinyBarkBehavior);
}
bulldog.dart
class Bulldog extends Dog {
  Bulldog() : super(bark: StrongBarkBehavior);
}
bark_behavior.dart
typedef BarkBehavior = void Function();
final StrongBarkBehavior = () => print('バウ!');
final TinyBarkBehavior = () => print('ワン!');

何が嬉しいの?

パッと見た限り、Strategy Patternのほうが煩雑に見えるかもしれません。ただ、変更には圧倒的にStrategy Patternのほうが強いです。
具体的なメリットとしては下記があげられます。

  • 特定の犬どうしで振る舞いを共通化できる
  • 機能追加で副作用を与えない

それぞれ説明させていただきます。

特定の犬どうしで振る舞いを共通化できる

新しい犬 ドーベルマン を追加することになりました。それぞれ対応してみましょう。

継承

doberman.dart
class Doberman extends Dog {
  @override
  void bark() => print('バウ!');
}

Strategy Pattern

doberman.dart
class Doberman extends Dog {
  Doberman() : super(bark: StrongBarkBehavior);
}

いかがでしょう?
継承を使ったパターンだと、ブルドックと同じ鳴き方をする(諸説ある)ドーベルマンにたいして、同じ処理を繰り返し書いてしまっています。これでは保守性が悪くなってしまいますね。

機能追加で副作用を与えない

犬は走るでしょ!という要望があり、機能の追加が入りました。それぞれ対応してみましょう。

継承

dog.dart

abstract class Dog {
  void bark();
  void run() => print('犬が走っています');
}

Strategy Pattern

dog.dart
abstract class Dog {
  const Dog({
    required this.bark,
    required this.run,
  });

  final BarkBehavior bark;
  final RunBehavior run;
}
toy_poodle.dart
class ToyPoodle extends Dog {
  ToyPoodle()
      : super(
          bark: TinyBarkBehavior,
          run: NormalRunBehavior,
        );
}
bulldog.dart
class Bulldog extends Dog {
  Bulldog()
      : super(
          bark: StrongBarkBehavior,
          run: NormalRunBehavior,
        );
}
run_behavior.dart
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の関数を呼び出すための関数が別途用意されていましたが、そもそもクロージャで渡すことにしたので用意していません。

参考文献

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?