はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
いまいちピンとこない例
いまいちピンとこないコード
で説明されてることが多く、
結局これっていつ使うの?
という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。
そこで特に「ゲーム開発ではどう使うか?」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。
デザインパターンを学ぶ理由
デザインパターンを学ぶ理由としては
- 車輪の再発明の防止
- 長文で読みにくいコード(可読性の低いコード)を減らす
- コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
- モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。
対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。
- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている
そのため、脱・初心者
中級者へのステップアップ
として デザインパターンを学ぶ
のが良いと思います。
デザパタ記事リンク
生成系
構造系
様態・ふるまい系
- Chain of Responsibility パターン
- Command パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- State パターン
- Strategy パターン
- TemplateMethod パターン
- Visitor パターン(本記事)
Visitor パターンについて
訪問者
を意味するVisitor パターン 。
ここでいう Visitor は Visitorを待っている側(Visitorパターンでは来訪者を受け入れるということで Acceptor
とも呼ばれます) にとっては なんらかのスペシャリスト
であることを期待します。
Acceptor からすると、スペシャリストのVisitorが訪れたら、家の中に招き入れて、あとはスペシャリストにお任せしますというスタンスをとります。
イメージとしては以下のような形になります。
一般的な例で言えば、Visitorは 家庭訪問する先生
だったり 水道トラブルを解決する会社の人
だったり リフォーム業者
のようなものをイメージしてもらえると良いでしょう。
このVisitor パターンですが、Acceptor は 自分に関連する処理
については関心を持っていない点が大事になります。
あくまでも来訪の受け入れを返すコールバック(Visitor.visit)のみ実行しますが、それ以降の処理はAcceptorは自信のインスタンスの参照を渡して、ご自由にどうぞという形で進めます。
つまり Acceptor 側に処理を追加しない
でVisitorが行うこと
は Visitorで管理すべき
という思想で設計・実装を行うということになります。
特にVisitorパターンが有効に働くのは for, foreach
等でまとめて処理する時で、各訪問先であるAcceptor毎に処理が異なる場合でも処理を一括で行うことができます。
public class SomeVisitor
{
void Main(){
List<IAcceptor> visitList = new List<IAcceptor>();
visiteList.Add( new HogeAcceptor() );
visiteList.Add( new PiyoAcceptor() );
visiteList.Add( new FugaAcceptor() );
visiteList.Add( new PiyoAcceptor() );
visiteList.Add( new PiyoAcceptor() );
visiteList.Add( new HogeAcceptor() );
foreach( var acceptor in visitList)
{
// クラスによって処理内容違うけど、まとめて処理する
acceptor.accept(this);
}
}
public void Visit(HogeAcceptor acceptor){
// Hogeに関する処理
// ex) Hogeの持っている畑に種を蒔く
}
public void Visit(FugaAcceptor acceptor){
// Fugaに関する処理
// ex) Fugaの持っている畑に水を撒く
}
public void Visit(PiyoAcceptor acceptor){
// Piyoに関する処理
// ex) Piyoの持っている畑で収穫する
}
}
先ほどのクラス図を見た時に なぜVisitor は visit(IAccpetor acceptor) という共通メソッドを持たないの?
と疑問に思うかもしれません。もし共通化すると、各クラスごとに処理を分けることが難しくなってしまいます(C#なら一応 is 演算子によって判断もできますが)。それよりは訪問先毎に受け入れ可能体制を整えておくというスタンスにしておくことで柔軟に拡張がしやすくなるという考え方に基づいているため、 意図的に各クラス毎にコールバック用APIを用意しています
。
ゲームにおける Visitor パターン
ゲームにおけるVisitorパターンは人によっては利用するかもしれませんが、あまり利用例としては一般的ではないかもしれません。
というのも、先に説明したような事例がほとんどなく、またゲームという特性上、パラメータ数が組合せ爆発が起こりやすく クラス毎に処理を分ける
ようなコードを用意すると、たちまちクラスが肥大化しやすくなります。
基本的に いかに処理を共通化して、固有処理を減らすか?
がゲーム開発では重要になってくるので利用されづらいのも仕方がないでしょう。
また、Visitor パターンのデメリットとして、 わざわざ共通インターフェースで呼び出して、処理は固有メソッドを用意する
手間がかかります。
それよりは、共通処理用のインターフェースを用意して Iterator パターン で対象をまとめて処理することが大半であると思われます。
まとめ
関心事の分離としては非常に有益なデザインパターンであるVisitorパターンですが、殊、ゲーム開発という文脈だとやや遠回りな手法だったり、拡張性と処理の共通化という部分が相反するため、そこまで頻繁に利用されるデザインパターンでは無いと思います。
そこまで必須のデザインパターンではないため、知識レベルで頭に入れておくのが良いでしょう。