はじめに
レビューや勉強会にて、早期returnやストラテジパターン・ステートパターンなどを教えていました。早期returnについては誰でもできるようになってきましたが、ストラテジパターン・ステートパターンはできる人とできない人にわかれました。
そこで誰にでもストラテジパターン・ステートパターンを使った実装までたどりつける方法を考えてみました。
目次
前提条件
今回はゲームの武器ごとのダメージ計算するメソッドを実装するとします。
ダメージの計算は(武器の攻撃力ー対象の防御力)×攻撃回数とし、攻撃力と攻撃回数は武器ごとに設定されているものとします。
一つのメソッドのみのコード
例1
switchの前で1つの変数を定義してswitch内で変数を設定しているコード
public int GetDamage(EnumWeapon enumWeapon, int defensePower)
{
//攻撃力を設定
int offensivePower = 0;
switch (enumWeapon)
{
case EnumWeapon.BareHands:
offensivePower = 30;
break;
case EnumWeapon.Sword:
offensivePower = 50;
break;
case EnumWeapon.Hammer:
offensivePower = 100;
break;
}
//基本ダメージを計算
int baseDamage = offensivePower - defensePower;
//攻撃回数を設定
int attackTimes = 0;
switch (enumWeapon)
{
case EnumWeapon.BareHands:
attackTimes = 7;
break;
case EnumWeapon.Sword:
attackTimes = 3;
break;
case EnumWeapon.Hammer:
attackTimes = 1;
break;
}
//最終ダメージを計算
int finalDamage = baseDamage * attackTimes;
return finalDamage;
}
例2
switchの前で2つの変数を定義してswitch内で変数を設定しているコード
public int GetDamage(EnumWeapon enumWeapon, int defensePower)
{
//攻撃力と攻撃回数を設定
int offensivePower = 0;
int attackTimes = 0;
switch (enumWeapon)
{
case EnumWeapon.BareHands:
offensivePower = 30;
attackTimes = 7;
break;
case EnumWeapon.Sword:
offensivePower = 50;
attackTimes = 3;
break;
case EnumWeapon.Hammer:
offensivePower = 100;
attackTimes = 1;
break;
}
//基本ダメージを計算
int baseDamage = offensivePower - defensePower;
//最終ダメージを計算
int finalDamage = baseDamage * attackTimes;
return finalDamage;
}
メソッドの切り出し
変数に値をセットするのは1度のみにするように説明して、メソッドに分離してもらいます。
攻撃力取得用のメソッド
private int GetOffensivePower(EnumWeapon enumWeapon)
{
switch (enumWeapon)
{
case EnumWeapon.BareHands:
return 30;
case EnumWeapon.Sword:
return 50;
case EnumWeapon.Hammer:
return 100;
default:
throw new Exception("エラー");
}
}
攻撃回数取得用のメソッド
private int GetAttackTimes(EnumWeapon enumWeapon)
{
switch (enumWeapon)
{
case EnumWeapon.BareHands:
return 7;
case EnumWeapon.Sword:
return 3;
case EnumWeapon.Hammer:
return 1;
default:
throw new Exception("エラー");
}
}
GetDamageの修正
public int GetDamage(EnumWeapon enumWeapon, int defensePower)
{
//攻撃力を設定
int offensivePower = GetOffensivePower(enumWeapon);
//基本ダメージを計算
int baseDamage = offensivePower - defensePower;
//攻撃回数を設定
int attackTimes = GetAttackTimes(enumWeapon);
//最終ダメージを計算
int finalDamage = baseDamage * attackTimes;
return finalDamage;
}
例1、例2をどちらを変更してもこの形になります。
ストラテジパターンの実装
メソッドの切り出しではメソッド内のSwitchの分岐が重複になってるので、その部分が良くないと説明し、インターフェイスを実装するように説明します。
1.武器のインターフェイスを作成
先ほど作成したメソッドをそのままインターフェイスのメソッドにします。
interface IWeapon
{
int GetOffensivePower();
int GetAttackTimes();
}
2.インターフェイスを継承した各武器クラスを作成
class WeaponBareHands : IWeapon
{
public int GetOffensivePower()
{
return 30;
}
public int GetAttackTimes()
{
return 7;
}
}
class WeaponSword : IWeapon
{
public int GetOffensivePower()
{
return 50;
}
public int GetAttackTimes()
{
return 3;
}
}
class WeaponHammer : IWeapon
{
public int GetOffensivePower()
{
return 100;
}
public int GetAttackTimes()
{
return 1;
}
}
3.武器のクラスを取得するメソッドを実装
private IWeapon CreateWeapon(EnumWeapon enumWeapon)
{
switch (enumWeapon)
{
case EnumWeapon.BareHands:
return new WeaponBareHands();
case EnumWeapon.Sword:
return new WeaponSword();
case EnumWeapon.Hammer:
return new WeaponHammer();
default:
throw new Exception("エラー");
}
}
4.GetDamageメソッドの修正
public int GetDamage(EnumWeapon enumWeapon, int defensePower)
{
//武器クラスの作成
IWeapon weapon = CreateWeapon(enumWeapon);
//攻撃力を設定
int offensivePower = weapon.GetOffensivePower();
//基本ダメージを計算
int baseDamage = offensivePower - defensePower;
//攻撃回数を設定
int attackTimes = weapon.GetAttackTimes();
//最終ダメージを計算
int finalDamage = baseDamage * attackTimes;
return finalDamage;
}
まとめ
メソッドの切り出しを中間にいれることでできる人が頭の中でやっている内容を表に出してあげることで誰もができるようになることを狙いました。
今回はどうしたらできるようになるかを重点をおいているためストラテジパターンがなぜ必要なのかという部分は書いていません。実際に誰かに説明するときはそのあたりも説明してあげると良いと思います。