2008年2月 のアーカイブ

やる夫がデザインパターンをやるようです 第3回

やる夫がデザインパターンをやるようです 第3回をはてなブックマークに追加 やる夫がデザインパターンをやるようです 第3回をdel.icio.usに追加 Yahoo!ブックマークに登録 やる夫がデザインパターンをやるようです 第3回をGoogle Bookmarksに追加

2008年2月28日 木曜日
≪第2回
第4回≫

昨日のやる夫で学ぶデザインパターン第2回では、「やる夫はアプリケーションの変更について知る必要がある」とまとめました。今日は、この問題をデザインパターンによる解決へと向かいたいと思います。

<アーキテクト>
punch()メソッドやkick()メソッド、fly()メソッドを共通の振舞いとして処理するヒーロークラスもあるし、固有の振舞いとして処理するヒーロークラスもある。
ところが、walk()や、jump()、crouch()(しゃがむ)というメソッドはどうだ。変更がかかる可能性はほとんど無い。全てのヒーローは共通して歩くしジャンプもすればしゃがむことが出来る。処理も変わらないだろう。
変更があるメソッドと、変更がないであろうメソッドを分けることができるな?
   / ̄ ̄\
 /   _ノ  \
 |    ( ●)(●)
. |     (__人__)
  |     ` ⌒´ノ
.  |         }
.  ヽ        }
   ヽ     ノ
   /    く
   |     \
    |    |ヽ、二⌒)
<やる夫>
       ____
     /⌒  ⌒\ ホジホジ
   /( ●)  (●)\
  /::::::⌒(__人__)⌒::::: \  まぁ、そうすね
  |    mj |ー'´      |
  \  〈__ノ       /
    ノ  ノ
<アーキテクト>
態度は気に食わんが理解したようだな。
では変更がかかる固有の処理とヒーロークラスでマトリクスにしてみろ。
   / ̄ ̄\
 /   _ノ  \
 |    ( ●)(●)
. |     (__人__)
  |     ` ⌒´ノ
.  |         }
.  ヽ        }
   ヽ     ノ
   /    く
   |     \
    |    |ヽ、二⌒)
やる夫は固有の動きをするpunch() / kick() / fly()処理をヒーローごとにまとめてみました。すると、punch()処理は数種類の処理としてまとめることが出来ました。
punch() : 振る舞いリスト
"何もしない(パンチ出来ない)"
"普通のパンチ(ヒーロー共通パンチ)"
"デビルパンチ"
"ゴムゴムのパンチ"
"じゃんけんグー"
"アンパンチ"
・・・
同じようにkick()やfly()をまとめていく内にやる夫はあることに気がつきます。
<やる夫>
これらの処理をインターフェイスの実装クラスとして、ヒーロークラスの外部に持ち出せばいいお。
この発想は到底シロウトにはできないお。天才確定したお
          ____
       / \  /\  キリッ
.     / (ー)  (ー)\
    /   ⌒(__人__)⌒ \
    |      |r┬-|    |
     \     `ー'´   /
    ノ            \
  /´               ヽ
 |    l              \
 ヽ    -一''''''"~~``'ー--、   -一'''''''ー-、.
  ヽ ____(⌒)(⌒)⌒) )  (⌒_(⌒)⌒)⌒))
<アーキテクト>
        / ̄ ̄\
      /       \
      |::::::        |  天才を除いてはそのとおりだ。
     . |:::::::::::     | 
       |::::::::::::::    |          ....,:::´, .
     .  |::::::::::::::    }          ....:::,,  ..
     .  ヽ::::::::::::::    }         ,):::::::ノ .
        ヽ::::::::::  ノ        (:::::ソ: .
        /:::::::::::: く         ,ふ´..
-―――――|:::::::::::::::: \ -―,――ノ::ノ――
         |:::::::::::::::|ヽ、二⌒)━~~'´
やる夫が思いついたのはこういうことです。

  • punch() → interface PunchBehavior
  • kick() → interface KickBehavior
  • fly() → interface FlyBehavior

これらのメソッドをインターフェイスとして定義します。ここまでは第2回で思いついたままですが、インプリメントはヒーロークラスではなく、ヒーロークラスと分離した外部のクラスで行うことにたどり着いたのです。
コードで表現すると以下のようになります。

interface PunchBehavior {
public abstract void punch();
}
class NormalPunch implements PunchBehavior {
public void punch(){ System.out.println( "パンチ!" ); }
}
// 他、パンチビヘイビアをインプリメントして振る舞いを作る。
// キックビヘイビア、フライビヘイビアも同様
そして、ヒーロークラスがこのインターフェイスを保持し、パンチの振る舞いや、キックの振る舞いを委譲すればよいわけです。
class Hero {
// 振舞いを保持する
protected PunchBehavior mPunchBhv = null;
// ビヘイビアクラスに処理を委譲する
public void performPunch(){ mPunchBhv.punch(); }
・・・・
これで解決できたのは、パンチという処理に変更が入った場合、以前のやる夫の発想では全てのヒーロークラスを見て回って修正しなければなりませんが、今回のこの方法を取るとビヘイビアクラスの修正により各々のヒーローに適用されていきます。
<アーキテクト>
解決できたことは他にもある。setPunch( PunchBehavior punchBhv )というプロパティセッターをヒーロークラスに用意することで、サザエさんでもゴムゴムのパンチが打てるようになる。
たとえば、「"ゴムゴムパンチの薬"というアイテムが登場させるから、サザエさんでもゴムゴムのパンチを打てるようにしろ。」という仕様が企画から降りてきても柔軟に対応できるわけだ。
   / ̄ ̄\
 /   _ノ  \
 |    ( ●)(●)
. |     (__人__)
  |     ` ⌒´ノ
.  |         }
.  ヽ        }
   ヽ     ノ
   /    く
   |     \
    |    |ヽ、二⌒)
<やる夫>
     ____
   /      \ ( ;;;;( (なるほど。よく理解できました)
  /  _ノ  ヽ__\) ;;;;) ウチのアーキテクトの話は長いお。。。
/    (─)  (─ /;;/
|       (__人__) l;;,´
/      ∩ ノ)━・'/
(  \ / _ノ´.|  |
.\  "  /__|  |
  \ /___
<アーキテクト>
      / ̄ ̄\ 
    /ノ( _ノ  \    ( )が逆だボケが
    | ⌒(( ●)(●)
    .|     (__人__) /⌒l
     |     ` ⌒´ノ |`'''|
    / ⌒ヽ     }  |  |              
   /  へ  \   }__/ /             / ̄ ̄\
 / / |      ノ   ノ           / ●)) ((●\’, ・
( _ ノ    |      \´       _    (   (_人_)’∴ ),
       |       \_,, -‐ ''"   ̄ ゙̄''―---└'´ ̄`ヽ   て
       .|                  ______ ノ    (
       ヽ           _,, -‐ ''"  ノ       ヽ   r'
         \       , '´        し/..     | J
          \     (           /      |
            \    \         し-  '^`-J
というわけで、以下にサンプルソースを載せておきます。やる夫で学ぶデザインパターンと銘打っている以上これはデザインパターンなのですが、ストラテジーパターンと呼ばれるものです。
正式な定義で記すとなんだか腹に落ちてこない文言なのですが、
Strategyパターンは一連の「アルゴリズム(処理)」を定義します。そしてそれらをカプセル化し、交換可能にします。Strategyパターンにより、このアルゴリズムを使用するクライアントに依存することなく、アルゴリズムそのものを変更することができます
ストラテジーパターンはオブジェクト指向の「カプセル化」の概念を理解するために有効です。ヒーロークラスが知っているのは、パンチを繰り出す方法だけです。そのパンチの処理方法は知る必要がありません。パンチのことに詳しいクラスに処理を委譲するのです。

ドラゴンボールの初期に出てきたポイポイカプセルではありませんが、このカプセルをポイっと投げるだけでバイクが出てきてそれに乗ることができます。どうやってバイクを出したかは知りませんが。
乱暴な言い方をすればそんな感じです。

ということで、やる夫で学ぶデザインパターン。今回はストラテジーパターンでした。他にもたくさんあるのでそのうちポストしてみます。やる夫で学ぶデザインパターンだけ串刺しで見たい人はタグのほうからどうぞ。

<サンプルソース>

import java.util.*;
public class YaruoStrategyPtn {
public static void main( String[] args ) {
Hero heroInst = null;
{
heroInst = new Sazae();
heroInst.performeAll();
// (アイテムをとって)振る舞いを変更
heroInst.setPunch( new GomugomuPunch() );
heroInst.performeAll();
}
}
};
// ヒーロースーパークラス
class Hero {
protected String name = "";
protected PunchBehavior mPunchBhv = null;
protected KickBehavior mKickBhv = null;
protected FlyBehavior mFlyBhv = null;
Hero(){ name = ""; }
public void walk(){ System.out.println( "歩きます" ); }
public void jump(){ System.out.println( "飛び跳ねます" ); }
public void crouch(){ System.out.println( "しゃがみます" ); }
public void setPunch( PunchBehavior punchBhv ){ mPunchBhv = punchBhv; }
public void setKick( KickBehavior kickBhv ){ mKickBhv = kickBhv; }
public void setFly( FlyBehavior flyBhv ){ mFlyBhv = flyBhv; }
public void performPunch(){ mPunchBhv.punch(); }
public void performKick(){ mKickBhv.kick(); }
public void performFly(){ mFlyBhv.fly(); }
public void display(){ System.out.println( name ); }
public void performeAll(){
display();
walk();
jump();
crouch();
mPunchBhv.punch();
mKickBhv.kick();
mFlyBhv.fly();
System.out.println( "パフォーマンス終了\n" );
}
}
class Sazae extends Hero {
Sazae(){
name = "サザエでございます";
mPunchBhv = new NormalPunch();
mKickBhv = new NormalKick();
mFlyBhv = new FlyNoWay();
}
}
class Devilman extends Hero {
Devilman(){
name = "デッビール!";
mPunchBhv = new DevilPunch();
mKickBhv = new DevilKick();
mFlyBhv = new FlyWithWing();
}
}
class Rufie extends Hero {
Rufie(){
name = "海賊王になるっ";
mPunchBhv = new GomugomuPunch();
mKickBhv = new NormalKick();
mFlyBhv = new FlyNoWay();
}
}
// ------------------------
// interface PunchBehavior
// ------------------------
interface PunchBehavior {
public abstract void punch();
}
class NormalPunch implements PunchBehavior {
public void punch(){ System.out.println( "パンチパンチシュッシュッ" ); }
}
class PunchNoWay implements PunchBehavior {
public void punch(){ System.out.println( "パンチなんて。。。できません" ); }
}
class GomugomuPunch implements PunchBehavior {
public void punch(){ System.out.println( "ゴムゴムのーぱぁーんち" ); }
}
class DevilPunch implements PunchBehavior {
public void punch(){ System.out.println( "デッビールパーンチ" ); }
}
// ------------------------
// interface KickBehavior
// ------------------------
interface KickBehavior {
public abstract void kick();
}
class NormalKick implements KickBehavior {
public void kick(){ System.out.println( "キックキックシュッシュッ" ); }
}
class KickNoWay implements KickBehavior {
public void kick(){ System.out.println( "蹴るなんて。。。できません" ); }
}
class DevilKick implements KickBehavior {
public void kick(){ System.out.println( "デッビールキーック" ); }
}
// ------------------------
// interface FlyBehavior
// ------------------------
interface FlyBehavior {
public abstract void fly();
}
class FlyWithWing implements FlyBehavior {
public void fly(){ System.out.println( "デッビールウィーング" ); }
}
class FlyWithEngine implements FlyBehavior {
public void fly(){ System.out.println( "エンジンで飛びますゴゴゴゴ" ); }
}
class FlyNoWay implements FlyBehavior {
public void fly(){ System.out.println( "飛ぶなんて。。。できません" ); }
}

やる夫がデザインパターンをやるようです 第2回

やる夫がデザインパターンをやるようです 第2回をはてなブックマークに追加 やる夫がデザインパターンをやるようです 第2回をdel.icio.usに追加 Yahoo!ブックマークに登録 やる夫がデザインパターンをやるようです 第2回をGoogle Bookmarksに追加

2008年2月28日 木曜日
≪第1回
第3回≫

やる夫は先日の失敗から継承について考えてみました。
Heroクラスを継承するヒーローたち全てが飛ぶわけではなかったのです。ということは、飛ばしてはいけない実装を個々のサブクラスにしてあげればよいわけです。

<やる夫>
     ____
   /      \ そういえばボケモントレーナーを実装したときに
  /  ─    ─\ punch()メソッドやkick()メソッドを
/    (●)  (●) \ オーバーライドしたな。。。
|       (__人__)    |
/     ∩ノ ⊃  /
(  \ / _ノ |  |
.\ “  /__|  |
  \ /___ /
class Hero {
punch()
kick()
jump()
display()
fly()
}
class ボケモントレーナー extends Hero {
punch(){
// ボケモントレーナー自身はパンチ出来ない。
// 保持しているボケモンにパンチの指示を出す。
}
kick(){
// ボケモントレーナー自身はキック出来ない。
// 保持しているボケモンにキックの指示を出す。
}
display(){
// ボケモントレーナー
}
}
<やる夫>
       ____
     /⌒  ⌒\ だったら、飛ばないヒーローは、fly()メソッドを
   /( ●)  (●)\ 空実装してやればいいお
  /::::::⌒(__人__)⌒::::: \
  |     |r┬-|     |
  \      `ー'´     /
やる夫は、Heroを継承するクラスにおいて、飛ばないヒーローはfly()メソッドをオーバーライドして何もしないようにすることを思いつきました。
class サザエさん extends Hero {
display(){
// サザエさんの表示
}
fly(){
// サザエさんは飛ばない。なにもしない。
}
}
<やる夫>
       ____
     /⌒  ⌒\ これで飛ばないヒーローたちを実現できるおww
   /( >)  (<)\ どう考えても天才の発想だおwww
  /::::::⌒(__人__)⌒::::: \
  |    /| | | | |     |
  \  (、`ー―'´,    /
       ̄ ̄ ̄
<アーキテクト>
お前はウチのきゅんぽな企画のこと知ってて言ってんのか?
ヒーローたちはリリースしても数ヶ月ごとにバージョンアップする。
ヒーローが追加される可能性だってあるわけだ。
てめぇはその度にfly()メソッドなんかを調べて
オーバーライドしていくのか?
        / ̄ ̄\
      /       \
      |::::::        |
     . |:::::::::::     |
       |::::::::::::::    |
     .  |::::::::::::::    }          ....:::,,  ..
     .  ヽ::::::::::::::    }         ,):::::::ノ .
        ヽ::::::::::  ノ        (:::::ソ: .
        /:::::::::::: く         ,ふ´..
-―――――|:::::::::::::::: \ -―,――ノ::ノ――
         |:::::::::::::::|ヽ、二⌒)━~~'´
<やる夫>
       ____
     /⌒  ⌒\ ホジホジ
   /( ●)  (●)\
  /::::::⌒(__人__)⌒::::: \  ?
  |    mj |ー'´      |
  \  〈__ノ       /
    ノ  ノ
やる夫はよくわかってないようですが、Heroクラスを継承している以上、そのサブクラスはfly()メソッドや、punch()メソッド、kick()メソッドに依存し続けなければなりません。
ということは、サブクラスが増える度にこれら全てのメソッドをチェックして、空実装でオーバーライドするか、固有の処理としてオーバーライドするか、あるいは、スーパークラスの処理をそのまま使うかなどを調査し、実装していくことになります。
いまのやる夫には、「ヒーローの型の一部だけがパンチしたり飛んだりキックしたりする」というわかりやすい方法が必要なのです。
<やる夫>
じゃあPunchableとかKickable、Flyableインターフェイスを
作ってこいつを必要なヒーローにインプリメントすりゃいいお。
       ____
     /⌒  ー、\ 
   /( ●)  (●)\
  /::::::⌒(__人__)⌒:::::\ 
  |     |r┬-/ '    |
  \      `ー'´     /
<アーキテクト>
   / ̄ ̄\
 /   _ノ  \
 |   ( ●)(●)  てめぇはもう喋るんじゃねー
. |     (__人__)____
  |     ` ⌒/ ─' 'ー\
.  |       /( ○)  (○)\
.  ヽ     /  ⌒(n_人__)⌒ \  
   ヽ   |、    (  ヨ    |
   /    `ー─-  厂   /
   |   、 _   __,,/     \
やる夫が発案した方法はJavaで言うinterfaceのimplementsです。
スーパークラスからパンチ、キック、フライメソッドを取り除き、interfaceとして定義します。それを必要なヒーローに実装してあげる方法ですが、例えばパンチという振る舞いにほんのちょっとした変更が共通的に加わった場合に、パンチを実装しているクラス全てに変更をして回る必要があります。いわゆる重複コードです。ではどうすればよいのでしょうか。。。

この「やる夫がデザインパターンをやるようです 第2回」では、ソフトウェア開発において考える必要のある大事なこと、「変更」について述べてみました。そもそもアプリケーションは「変更」され続け、成長・変化していくものです。そうでないとユーザは使ってくれません。ですが、その変更部分をソースにばら撒くようなことはしたくありません。今のやる夫はそれを知る必要があります。

というわけで、第2回はここで終わります。

≪第1回
第3回≫

やる夫がデザインパターンをやるようです 第1回

やる夫がデザインパターンをやるようです 第1回をはてなブックマークに追加 やる夫がデザインパターンをやるようです 第1回をdel.icio.usに追加 Yahoo!ブックマークに登録 やる夫がデザインパターンをやるようです 第1回をGoogle Bookmarksに追加

2008年2月26日 火曜日
第2回»

やる夫は、ニコニコゲーム製作会社に勤めています。ニコニコゲームはニンテンドゥープラットフォームで製作された「スーパーヒーロー大戦」を昨年リリースし空前の大ヒットを飛ばしました。もちろん第2弾を発売することとなり、やる夫はその第2弾のスパヒロ大戦2.0のプログラマとして携わっています。

スパヒロ大戦2.0のアーキテクトは、オブジェクト指向技術を使用し、登場する全てのヒーローの型が継承するHeroスーパークラスを作成しました。

   / ̄ ̄\   <アーキテクト>
 /   _ノ  \
 |    ( ●)(●)
. |     (__人__) Heroスーパークラス作ったから
  |     ` ⌒´ノ  てめぇが作るヒーローはこいつを継承して使えや
.  |         }
.  ヽ        }
   ヽ     ノ
   /    く
   |     \
    |    |ヽ、二⌒)
         |
     \  __  /
     _ (m) _
        |ミ|
      /  `´  \
       ____
     /⌒  ⌒\    <やる夫>
   /( ●)  (●)\   わかったおwww余裕だおww
  /::::::⌒(__人__)⌒::::: \
  |     |r┬-|     |
  \      `ー'´     /
class Hero {
punch()
kick()
display()
}
//> add yaruo hero class 20080226
class ガンダム extends Hero {
display(){
// ガンダムの表示
}
}
class アトム extends Hero {
display(){
// アトムの表示
}
}
class ウルトラマン extends Hero {
display(){
// ウルトラマンの表示
}
}
・・・・ほかにもたくさん
//< add yaruo 20080226
今回のスパヒロ大戦2.0に登場するヒーローは30人を超えます。やる夫は徹夜に徹夜を重ねやっとのことで全ヒーロー分のベータ版の実装を終えました。
               _______
    :/ ̄| :  :  ./ /  #  ;,;  ヽ
  :. | ::|    /⌒  ;;#  ,;.;::⌒ : ::::\ :
    | ::|:  / -==、   '  ( ●) ..:::::|
  ,―    \   | ::::::⌒(__人__)⌒  :::::.::::| : で、できたお。。。
 | ___)  ::|: ! #;;:..  l/ニニ|    .::::::/
 | ___)  ::|  ヽ.;;;//;;.;`ー‐'ォ  ..;;#:::/
 | ___)  ::|   .>;;;;::..    ..;,.;-\
 ヽ__)_/ :  /            \
しかしながら、ゲーム企画部は「今回のヒーローは空を飛ばすことで競合他社に勝利する」と決定し仕様の追加実装が、開発部に降りてきました。
ふむ。スーパークラスにfly()メソッドを追加すりゃ
全ヒーローが飛べるようになる。
     ____
   /      \
  /  ─    ─\
/    (●)  (●) \
|       (__人__)    |
/     ∩ノ ⊃  /
(  \ / _ノ |  |
.\ “  /__|  |
  \ /___ /
こんなんよゆうだおwwwww
          ____
        /_ノ  ヽ、_\
 ミ ミ ミ  o゚((●)) ((●))゚o      ミ ミ ミ
/⌒)⌒)⌒. ::::::⌒(__人__)⌒:::\   /⌒)⌒)⌒)
| / / /     |r┬-|    | (⌒)/ / / //
| :::::::::::(⌒)    | |  |   /  ゝ  :::::::::::/
|     ノ     | |  |   \  /  )  /
ヽ    /     `ー'´      ヽ /    /
 |    |   l||l 从人 l||l      l||l 从人 l||l
 ヽ    -一''''''"~~``'ー--、   -一'''''''ー-、
  ヽ ____(⌒)(⌒)⌒) )  (⌒_(⌒)⌒)⌒))
class Hero {
punch()
kick()
display()
// add yaruo 20080226
fly(){ /*ヒーローを飛ばすいろんな処理*/ }
}
やる夫はスーパークラスにfly()メソッドを追加し、これを継承するサブクラスは全て飛べるように対応しました。しかし。。。

ニンテンドゥー営業部も立ち会うキャラクターデモンストレーション当日、開発部に電話がかかってきます。
「おう。アーキテクトだ。てめぇの実装したヒーローをニンテンドゥーさんに見せたとこなんだが、ウーロン茶を液晶モニターに吹き付けられた。わかるか?いったいなんだってサザエさんが空を飛んでんだ?サザエさんは空をとばねぇ。そんくらいこのとはわかるな?てめぇのおかげで、次にお見せしようとしているヒーロー"ドカベン"をwktkして待っていらっしゃる。」

  あんな要求だったから
  挙動なんて大して確認してないお。。。。
       ______
      /  \    /\
    /  し (>)  (<)\
    | ∪    (__人__)  J | ________
    \  u   `⌒´   / | |          |
    ノ           \ | |          |
         ____
      /  \    ─\   チラッ
    /  し (>)  (●)\
    | ∪    (__人__)  J | ________
    \  u   `⌒´   / | |          |
    ノ           \ | |          |
         ____
      /::::::─三三─\
    /:::::::: ( ○)三(○)\
    |::::::::::::::::::::(__人__)::::  | ________
     \:::::::::   |r┬-|  / | |          |
    ノ::::::::::::  `ー'´   \ | |          |
やる夫は全てのサブクラスが飛ぶべきではないことに気がつきませんでした。ではどのような設計を行えばよかったのでしょう。
というわけで、第1回はここでおわります。
※ワタシはゲーム開発の進め方・開発ワークフローは全く知りませんのでご了承ください。馬鹿にしているわけじゃありません

第2回»