最近ポケモンソードシールドが発売され、大人気でございます。
というわけで、このポケモン人気に便乗してポケモンを使ってオブジェクト指向プログラミングを説明します。
オブジェクト指向プログラミングとは?
オブジェクトを直訳すると「もの」。すなわち、物を作るようにプログラミングをしようというわけです。
ものには、特徴だったりパーツだったり動作があるはずです。ものの種類を「クラス」、ものそれぞれを「インスタンス」、ものの動作を「メソッド」と呼びます。
例えば、ポケモンのリザードンを考えてみましょう。リザードンには名前・レベル・HP・攻撃などのステータスがあります。
リザードンという種族(クラス)には、「りざすけ」「りざまる」などいろんな名前のリザードン(インスタンス)がいます。
また、リザードンが覚える技の1つとして火炎放射があります。これがメソッドとなります。
これらをコード化すると以下のようになります。
#リザードンクラス
class Charizard(object):
#インスタンスを作ったときに動く部分(いわゆる初期設定)
def __init__(self,name,level,hp,attack,defence,sp_attack,sp_defence,speed):
self.name = name
#値の定義以外もできる
print('いけ!{}!'.format(self.name))
self.level = level
self.type1 = 'ほのお'
self.type2 = 'ひこう'
self.hp = hp
self.attack = attack
self.defence = defence
self.sp_attack = sp_attack
self.sp_defence = sp_defence
self.speed = speed
#火炎放射(メソッド)
def flame_radiation(self,enemy):
#相性補正
compatibility_coefficient = 1
for Type in [enemy.type1,enemy.type2]:
if Type == 'くさ'or'むし'or'こおり'or'はがね':
compatibility_coefficient *= 2
#ダメージ計算(メソッド使用時はこの式がわからなくても良い(カプセル化))
damage = ((self.level*2/5+2)*(90*self.sp_attack/enemy.sp_defence)/50+2)*compatibility_coefficient
print('{}の火炎放射!'.format(self.name))
print('{}は{}に{}のダメージを与えた!'.format(self.name,enemy.name,np.round(damage)))
if compatibility_coefficient >= 2:
print('こうかはばつぐんだ!!')
#これはインスタンス
rizasuke = Charizard('りざすけ',100,297,183,192,317,206,328)
#これもインスタンス
rizamaru = Charizard('りざまる',50,200,100,100,150,100,150)
一度クラスを読み込めば、数行のコードで簡単に実行することができます。
ちなみにメソッドに関しては、面白いことに別のクラスを対象にしても実行できます。
試しにフシギバナクラスを作り、フシギバナクラスに対して火炎放射メソッドを実行してみましょう。
class Fushigibana(object):
def __init__(self,name,level,hp,attack,defence,sp_attack,sp_defence,speed):
self.name = name
print('いけ!{}!'.format(self.name))
self.level = level
self.type1 = 'くさ'
self.type2 = 'どく'
self.hp = hp
self.attack = attack
self.defence = defence
self.sp_attack = sp_attack
self.sp_defence = sp_defence
self.speed = speed
rizasuke = Charizard('りざすけ',100,297,183,192,317,206,328)
fussi = Fushigibana('ふっしー',100,364,180,202,328,236,196)
rizasuke.flame_radiation(fussi)
##実行結果
#いけ!りざすけ!
#いけ!ふっしー!
#りざすけの火炎放射!
#りざすけはふっしーに414.0のダメージを与えた!
#こうかはばつぐんだ!!
オブジェクト指向プログラミングの大きな特徴として、以下の3つがあります。
・カプセル化
・継承
・ポリモーフィズム
カプセル化(情報隠蔽)
先程の火炎放射メソッドの中に、ダメージ計算式がありました。見た目からして複雑そうな式です。
しかし、実際にメソッドを実行するときは、rizasuke.flame_radiation(fussi)
と書くだけで容易に実行できます。ダメージ計算式を知る必要はありません。
一般的な例でいうと、print()関数も同じです。簡単に出力できる関数ではありますが、その処理内容がどうなっているか知っている人はごく小数でしょう。
このように、クラス内部の情報や処理が隠せることを、カプセル化と呼びます。カプセル化によって内部情報を書き換えられないようにできたり、クラスやメソッドの理解が簡単になります。
また、ステータスなどの情報を1つのクラス内にまとめて書くので、管理が楽になります。
継承
先程のコードのように特徴や動作がまとめられるだけでもオブジェクト指向プログラミングは非常に便利なものですが、ポケモンは今や890種類(wikipediaより)と言われています。ポケモンそれぞれのメソッドを1から書くのは面倒です。
そこで便利なのが「継承」(オーバーライド)です。やり方は、小クラスの引数に親クラスを入れた上で、super().継承したいメソッド
を入れるだけです。
ここでは、HPや攻撃などのステータス・火炎放射などの技を共有するためにポケモンクラスを設定しています。
以下のコードのように新たなポケモンクラス(親クラス)を実装し、リザードンやカイリューなどの子クラスへ継承することで、ステータスや技のコードを二重に書く必要がなくなります。これなら、890種のポケモンも難なく実装できそうです。
#親クラス
class Pokemon(object):
def __init__(self,name,level,hp,attack,defence,sp_attack,sp_defence,speed):
self.name = name
print('いけ!{}!'.format(self.name))
self.level = level
self.type1 = None
self.type2 = None
self.hp = hp
self.attack = attack
self.defence = defence
self.sp_attack = sp_attack
self.sp_defence = sp_defence
self.speed = speed
#火炎放射
def flame_radiation(self,enemy):
#相性補正
compatibility_coefficient = 1
for Type in [enemy.type1,enemy.type2]:
if Type == 'くさ'or'むし'or'こおり'or'はがね':
compatibility_coefficient *= 2
damage = ((self.level*2/5+2)*(90*self.sp_attack/enemy.sp_defence)/50+2)*compatibility_coefficient
print('{}の火炎放射!'.format(self.name))
print('{}は{}に{}のダメージを与えた!'.format(self.name,enemy.name,np.round(damage)))
if compatibility_coefficient >= 2:
print('こうかはばつぐんだ!!')
#リザードン(子クラス)
class Charizard(Pokemon):
def __init__(self,name,level,hp,attack,defence,sp_attack,sp_defence,speed):
super().__init__(name,level,hp,attack,defence,sp_attack,sp_defence,speed)
self.type1 = 'ほのお'
self.type2 = 'ひこう'
def flame_radiation(self,enemy):
super().flame_radiation(enemy)
#カイリュー(子クラス)
class Cairyu(Pokemon):
def __init__(self,name,level,hp,attack,defence,sp_attack,sp_defence,speed):
super().__init__(name,level,hp,attack,defence,sp_attack,sp_defence,speed)
self.type1 = 'ドラゴン'
self.type2 = 'ひこう'
def flame_radiation(self,enemy):
super().flame_radiation(enemy)
#カイリューの火炎放射にだけつく特性(ポリモーフィズム説明のための仮特性)
print('相手はやけどになった!')
rizasuke = Charizard('りざすけ',100,297,183,192,317,206,328)
ryu_kun = Cairyu('りゅーくん',100,323,403,226,212,299,196)
rizasuke.flame_radiation(ryu_kun)
ryu_kun.flame_radiation(rizasuke)
##実行結果
#いけ!りざすけ!
#いけ!りゅーくん!
#りざすけの火炎放射!
#りざすけはりゅーくんに329.0のダメージを与えた!
#りゅーくんの火炎放射!
#りゅーくんはりざすけに319.0のダメージを与えた!
#相手はやけどになった!
ポリモーフィズム
ポリモーフィズムとは、直訳すると多態性・多相性・多様性と呼ばれるものです。多様性が最も馴染み深いかと思います。
継承と併用することで、同じ命令を出しても違う結果(多様な結果)を出せることが大きな特徴です。
継承の例で出したコードをもう一度見てください。カイリューの火炎放射メソッドの中に、super().flame_radiation(enemy)
に加えてprint('相手はやけどになった!')
が入っています。リザードンの火炎放射メソッドの中には入っていません。
このようにすると、リザードンの火炎放射はやけどせず、カイリューの火炎放射は必ずやけどする仕様にできます(実際のポケモンはこんな仕様ではありません)。
まとめ
「ポケモンで学ぶオブジェクト指向プログラミング」自体は二番煎じのネタですが、カプセル化や継承などオブジェクト指向プログラミングの3大要素について説明したものはありません。そこらへんがうまく伝わればと思います。
ただ、習うより慣れろで、実際に書いて見たほうが理解は絶対深まりますし、オブジェクト指向プログラミングのメリットも少しずつ体感で分かってきます。
初心者の方は、オブジェクト指向プログラミングなんて難しそうと敬遠せず、ぜひ一度自分でいろいろと書いてみてください。
参考文献
オブジェクト指向がわからない! そんなあなたの脳味噌をオブジェクト脳にする準備体操
【Python】オブジェクト指向プログラミングの概念と書き方