この記事では、コードの可読性や保守性を向上させるための「ポリシーパターン」の活用方法を解説します。
特にRPGゲームのような場面で複数の条件分岐が発生するコードを例に、条件分岐の重複やネストの問題をどのように解決できるか、サンプルコードを使ってご紹介します。
本記事ではC#のコードを例に用いていますが、ポリシーパターンは他のプログラミング言語でも応用可能なデザインパターンです。
ポリシーパターンとは
ポリシーパターンとは、条件分岐やロジックを個別のクラス(ポリシークラス)に分けて管理するデザインパターンの一つです。特に、複雑な条件分岐や、頻繁に変更が発生するロジックに対応する際に有効です。このパターンを用いると、条件ごとに独立したクラスがそれぞれのロジックを実装するため、コードの再利用性や拡張性が向上します。
ポリシーパターンでは、インターフェースを介して共通の条件やルールを実装し、状況に応じたポリシークラスがそれぞれの条件に基づいて振る舞いを定義します。これにより、クライアント側のコードは各条件分岐に対応する処理を直接記述する必要がなくなり、柔軟かつ簡潔なコード設計が可能になります。
例えば、RPGゲームのキャラクターが特定の条件でスキルを使用できるかどうかを判断する際、キャラクターのクラスやレベルに応じて条件が変わるといった場合に有効です。ポリシーパターンを使えば、条件ごとのロジックが個別のクラスに分けられ、新しい条件が追加されても、他のコードに影響を与えずに対応できます。
悪い例のサンプルコード:複雑な条件分岐
以下は、RPGゲームにおけるキャラクターが特定のスキルや攻撃を使えるかどうかを判定するコードです。この例では、キャラクターの「クラス」や「レベル」、「特定のアイテム」を条件とした複数の分岐が入り組んでおり、条件が複雑で読みづらく、保守も困難です。
public class Character
{
public int Level { get; set; }
public bool HasMagicRing { get; set; }
public string ClassType { get; set; }
public bool CanUseSpecialAttack()
{
if (Level > 10)
{
if (ClassType == "Warrior")
{
if (HasMagicRing)
{
return true;
}
return false;
}
else if (ClassType == "Mage")
{
if (Level > 15)
{
return true;
}
return false;
}
return false;
}
return false;
}
}
このコードでは、クラスやレベル、アイテムによって条件分岐が多層になっており、新しいクラスや条件を追加するとさらにネストが増えてしまいます。可読性が低く、条件を変更したり追加する場合にエラーの原因にもなりやすいです。
ポリシーパターンによるリファクタリング
上記のようなコードをポリシーパターンでリファクタリングすることで、各条件をインターフェースを通じて個別のポリシークラスに分けられ、条件分岐の重複やネストを解消できます。
ポリシーパターンを使用すると、各条件が独立したクラスで実装されるため、新しい条件が追加されてもメインクラスに影響を与えずに済みます。また、instanceofなどの判別も不要になり、リスコフの置換原則を遵守した設計が可能です。
良い例のサンプルコード:ポリシーパターンでの実装
次に、ポリシーパターンを用いてリファクタリングした例を示します。ここでは、IAttackPolicyインターフェースを使用して、キャラクターのクラスごとにスキル判定のロジックをポリシークラスに分けています。Characterクラスは条件分岐を意識する必要がなくなり、コードが簡潔で拡張性の高いものに改善されます。
public interface IAttackPolicy
{
bool CanUseSpecialAttack(Character character);
}
public class WarriorAttackPolicy : IAttackPolicy
{
public bool CanUseSpecialAttack(Character character)
{
return character.Level > 10 && character.HasMagicRing;
}
}
public class MageAttackPolicy : IAttackPolicy
{
public bool CanUseSpecialAttack(Character character)
{
return character.Level > 15;
}
}
public class Character
{
public int Level { get; set; }
public bool HasMagicRing { get; set; }
public string ClassType { get; set; }
private IAttackPolicy attackPolicy;
public Character(string classType)
{
ClassType = classType;
attackPolicy = ClassType switch
{
"Warrior" => new WarriorAttackPolicy(),
"Mage" => new MageAttackPolicy(),
_ => throw new ArgumentException("Invalid class type")
};
}
public bool CanUseSpecialAttack()
{
return attackPolicy.CanUseSpecialAttack(this);
}
}
この実装では、キャラクターのクラスに応じたAttackPolicyが選択され、各ポリシークラスで条件を処理しています。新しいクラスや条件が追加される際も、既存コードに影響を与えずに拡張できるため、メンテナンスがしやすくなります。
ポリシーパターンを利用するメリット
ポリシーパターンを導入することにより、以下のメリットが得られます:
- コードの再利用性が向上:各ポリシーが独立したクラスで定義されるため、再利用が容易です。
- 可読性が向上:条件分岐のネストが解消され、メインクラスのコードがシンプルになります。
- 拡張性が向上:新しい条件やキャラクタータイプの追加が容易になり、既存のコードに影響を与えません。
- リスコフの置換原則を遵守:
instanceofによる型判定を避けることで、サブタイプは基底タイプとして置き換え可能になり、柔軟性と安全性が向上します。
まとめ
- ポリシーパターンを用いることで、複雑な条件分岐を個別のポリシークラスに分割し、コードの保守性と拡張性を向上させられる。
- 条件ごとにクラス化することで、再利用性が高まり、読みやすいコードが実現する。
- リスコフの置換原則を遵守し、
instanceofを使った型判定を避けることで、堅牢で安全なコード設計が可能になる。
複雑な条件分岐は、コードの可読性や保守性を低下させがちです。ポリシーパターンを活用することで、これらの問題を解決し、拡張性の高いコードが実現できます。
この記事を参考に、プロジェクトにおける条件分岐が増えた際には、ポリシーパターンの導入を検討してみてください。よりシンプルでメンテナンスしやすい設計が実現できるでしょう。
