ストラテジパターンについて

概要

ストラテジパターンとは、デザインパターン の一種である。

概要としては、例えばifswitch文などを使用せずに、分岐処理をクラスやインスタンス、インターフェースなどを使用して実現する方法、手法である。

ここでは、例えば魔法攻撃をswitch文で処理するコードを記述してみる。
Kotlinでは、when文でswitchと同じ役割を果たす。

void getMagicAttackPower(magicNameArg : String) -> Int {
	var result : Int = 0;
	
	when(magicNameArg){
		"fire" -> result = 30;
		"water" -> result = 50;
		"poison" -> result = 20;
		
		...
		
	}
	
	return result;
}

このコードでは、魔法攻撃の名前を入力するとそれに応じた攻撃力が返ってくるメソッドである。

さらに、魔法攻撃の消費MPも計算するメソッドを作成したとする。

void getMagicCosumedMP(magicNameArg : String) -> Int {
	var result : Int = 0;
	
	when(magicNameArg){
		"fire" -> result = 30;
		"water" -> result = 50;
		"poison" -> result = 20;
		
		...
		
	}
	
	return result;
}

ここで仕様変更してさらに魔法が追加されたとする。

  • “wind”魔法の追加
void getMagicAttackPower(magicNameArg : String) -> Int {
	var result : Int = 0;
	
	when(magicNameArg){
		"fire" -> result = 30;
		"water" -> result = 50;
		"poison" -> result = 20;
		"wind" -> result = 40;
		
		...
		
	}
	
	return result;
}

しかし、ほどなくしてMPが消費されないという問題が発生した。
これの原因を探って見るとgetMagicCosumedMPメソッドにて、wind魔法のロジックが実装されていないことに気づいた。

担当者が、getMagicCosumedMPメソッドにてwind魔法のロジックを実装し忘れていたのだ。

ここでは魔法の種類は4種類だが、実際のアプリでは何十個にもわたるため、このようなことが頻繁に起こりえる。

そこで、魔法のタイプをそれぞれクラスとして分けることにした。
まず初めに、interfaceとしてMagicInterfaceを作成して、プロパティを定義する。

ここでは、魔法の名前とダメージ量と消費MPを定義することにした。

interface MagicInterface{
	val name : String;
	val damageAmount : Int;
	val consumedMP : Int;
	
	fun getName() : String;
	fun getDamageAmount() : Int;
	fun consumedMP() : Int;

そして、各魔法ごとに定義した。ここではfireのみ例に挙げる。

class Fire() : MagicInterface{
	val name : String = "fire";
	val damageAmount : Int = 20;
	val consumedMP : Int = 5;
	
	override fun getName(): String = name;
	override fun getDamageAmount() : Int = damageAmount;
	override fun consumedMP() : Int = consumeMP;
}

interfaceを継承すると記入漏れが無くなる。

さらに、MagicContextクラスを用意して単一のクラスで管理できるようにする。

class MagicContext(context: MagicInterface) {
	fun getName(): String = context.getName;
	fun getDamageAmount() : Int = context.getDamageAmount;
	fun consumedMP() : Int = context.getConsumeMP;
}

さらに、これらの魔法をmapで管理できるようにする。

このcontextクラスを使用すると以下の通りになる。

main(){
	val fire = Fire()
	var context = MagicContext(fire)
	print("magic name = ${context.getName}") // "fire"
	
	val water = Water()
	context = MagicContext(water)
	print("magic name = ${context.getName}") // "water"
}

さらにmapでまとめる。

main(){
	val map = hashMapOf<String, MagicInterface>();
	map["fire"] = Fire()
	map["poison"] = Poison()
	map["water"] = Water()
	map["wind"] = Wind()
	
	map["fire"].getName // "fire"
	map["water"].getName // "water"
}

こうすることで、ifswitch文を削減することが出来る。