はじめに
どのプログラミング言語から勉強するにしても、共通して「コードはどう書くべきか」、つまり「どのようにソフトウェアを設計するべきか」という課題に直面するときがあると思います。私自身それについて考える機会があり、**「オブジェクト指向プログラミング」**というソフトウェアの設計手法に改めて向き合ってみました。考え方など、自身の中に落とし込むことに苦労したため、理解のプロセスを備忘録として残してみました。この記事はプログラミングの基礎をある程度理解している人であれば、全く問題なく読み進められるように作りましたので、是非最後まで読んでみてください。(コード解説はPythonを使用しました)
コードはどう書くべきなのか
私たちは何か作りたいものがあるときに、コードを書き、実装していくと思いますが、特にチームを組んで開発するような大規模なプロダクトに関わるときには**「綺麗な、読みやすい」コードを書くことが求められます。つまりプロダクトに関して、コードを書いた本人でなくとも、修正しやすい、新しい機能を追加しやすいといった「効率性」を目的としています。ではコードを書くときは、どのようなことを意識するべきなのでしょうか。大きく分けて答えは、「flexibility(柔軟性)」と「scalability(拡張性)」です。「flexibility(柔軟性)」は、修正したい箇所があるときに柔軟に対応できるのかを表し、「scalability(拡張性)」は、新しく要素や機能を追加したいときに、その規模に関係なく対応できるのかを表します。コードを修正するにしろ、追加するにしろ、コードは誰が読んでも理解しやすいものでなくてはならないため、以上の2つを意識します。そこでオブジェクト指向プログラミング**という概念が出てきます。
オブジェクト指向プログラミングとは
**オブジェクト指向プログラミング
互いに密接な関連性を持つデータとコードをひとつにまとめてオブジェクトとし、それぞれ異なる性質と役割を持たせたオブジェクトの様々な定義と、それらオブジェクトを相互に作用させる様々なプロセスの設定を通して、プログラム全体を構築するソフトウェア開発手法である。(引用:wikipedia)
オブジェクトとは、文字列やリストのようなデータ型のことです。つまり「データとコードをひとつにまとめてオブジェクトとし...」という文言がありますが、これは新しいデータ型を作ることを表します。したがってオブジェクト指向プログラミングを用いることで、アプリケーションの機能ごとに新しいデータ型を作成し、処理を分かりやすく分離することができるため、前述した「flexibility(柔軟性)」と「scalability(拡張性)」のあるプログラムを構築することができます。続けてより分かりやすく説明するために実際にオブジェクト指向プログラミングを用いて、今回はあるゲームを実装していきます。
開発する上で意識すべきこと
実際にオブジェクト(新しいデータ型)を作成していく上で意識することは、「機能ごとにどう処理を分けていくか」です。どのように機能ごとに処理を分けるかは、下の図で示しています。下の図はチャットボットを開発する際に、オブジェクト指向プログラミングを用いている場合と、いない場合の処理の分け方を表したものです。チャットボット1つをとっても、「メッセージの受信」、「応答メッセージの作成」など機能ごとに分類することができます。
オブジェクト指向を用いることで、何か新しい機能を追加したいとき、既にある機能を修正したいときに、どこのプログラムに手を加えれば良いのか一目で分かるため、余計な箇所を触る心配もなく、予期せぬエラーを防ぐことができます。オブジェクト指向プログラミングを用いて実装する上で意識するポイントを確認したところで、実例を用いてオブジェクト指向プログラミングがいかにソフトウェアの設計に有用か説明します。
オブジェクト指向プログラミングによる実装
オブジェクト指向プログラミングを用いて実装していく上で、今回はあるゲームを実例として扱います。シンプルに好きな果物が何なのか当てるゲーム(Guessing Game)です。流れとしては、まずユーザーに果物の名前を入力してもらい、アルファベットで入力されているか確認、正解であるappleが入力されていれば処理が完了するようなプログラムとしました。このゲームを、オブジェクト指向プログラミングを用いずに実装すると下記のコードのようになるかと思います。
オブジェクト指向なし
guess = 0
while True:
fruit = input("What do you think is my favorite fruit? : ")
if not fruit.isascii():
print("Please enter in English.")
continue
if fruit != "apple":
guess += 1
print("Thats not it!")
else:
print(f"You got it! You missed {guess}.")
break
とりあえず果物当てゲームを実装することを優先して考えると、上記のguessing_game.pyでも問題ないのですが、このプログラムでは**「flexibility(柔軟性)」と「scalability(拡張性)」**を満たしてはいません。具体的には、このゲームに新しく「5回以上間違えたらヒントを与える」というような機能を追加しようとしたときはどうでしょうか。上のコード例では、どこをどのように書き換え、新しいコードを追加するべきか一目では分かりにくいと思います。これがオブジェクト指向プログラミングを用いずに開発されたソフトウェアの問題です。では次にオブジェクト指向プログラミングを用いて、同じゲームを実装してみましょう。
オブジェクト指向あり
class GuessFruit:
def __init__(self, fruit):
self.fruit = fruit
self.guess = 0
def valid_fruit(self, en_fruit):
if en_fruit.isascii():
return en_fruit
else:
return False
def get_guess(self):
fruit_guessed = input("What do you think is my favorite fruit? : ")
if self.valid_fruit(fruit_guessed):
return fruit_guessed
else:
print("Please enter in English.")
return self.get_guess()
def play(self):
while True:
answer = self.get_guess()
if answer != self.fruit:
self.guess += 1
print("Thats not it!")
else:
break
print(f"You got it! You missed {self.guess}.")
game = GuessFruit("apple")
game.play()
コード量自体は、オブジェクト指向を用いない場合と比較して長くなってしまいますが、機能ごとに関数が定義されているので、どこに何が書かれているのかが分かりやすいと思います。加えて先ほどの課題であった新しく「5回以上間違えたらヒントを与える」機能の追加も、オブジェクト指向の考えを用いた上記のコード例では容易なはずです。新しい機能を追加したいのなら、新しい関数を定義してしまえば良いのです。
新機能を追加
class GuessFruit:
# 追加
def __init__(self, fruit, h_words, h_criteria=5):
self.fruit = fruit
self.guess = 0
self.h_words = h_words
self.h_criteria = h_criteria
def valid_fruit(self, en_fruit):
if en_fruit.isascii():
return en_fruit
else:
return False
def get_guess(self):
fruit_guessed = input("What do you think is my favorite fruit? : ")
if self.valid_fruit(fruit_guessed):
return fruit_guessed
else:
print("Please enter in English.")
return self.get_guess()
# 追加
def give_hint(self):
if self.guess >= self.h_criteria:
return self.h_words
else:
return False
def play(self):
while True:
answer = self.get_guess()
if answer != self.fruit:
self.guess += 1
print("Thats not it!")
# 追加
if self.give_hint():
print(f"Hint: {self.h_words} / Think again!")
else:
break
print(f"You got it! You missed {self.guess}.")
game = GuessFruit("apple", "The fruit's color is red.")
game.play()
このようにクラスの中に新しい関数を作成するだけで、機能を追加することができます。またオブジェクト指向プログラミングのポイントは、どのデータを外部から扱えるようにするかです。今回の例で考えれば、ゲームによっては「正解の果物の名前」、「ヒントの文」、「何回間違えたらヒントを与えるか」などのデータを変更したい場合も考えられるため、インスタンス化をする際に自由に値を決められるようにどのデータを引数として設定するかもポイントです。以上のようにオブジェクト指向プログラミングを用いて実装することで、コードを条件ごとに書き換える必要はなく、インスタンス化時に引数を指定してあげるだけで、多様な条件を持つプログラムを開発することができます。
まとめ
実例を通して示した通り、オブジェクト指向プログラミングは非常に有用な開発手法です。加えて、クラスを最小単位としてプログラムを設計する方法論とも呼ばれるように、大規模なプロジェクトになるほど真価を発揮する開発手法でもあります。プロジェクトが大規模になればなるほどクラスの数は増えて、パッケージ、ライブラリのように複雑化していきます。ただオブジェクト指向プログラミングを用いて実装されたソフトウェアは分解していくと最後は必ずクラスに辿り着きます。クラスに辿り着けば、誰であっても読みやすく、機能が追加しやすく、修正しやすいようなプログラムを見ることができるため、プロジェクトが大規模化、複雑化しても理解に苦しむことは少なくなります。
おわりに
ここまで読んでいただきありがとうございます。私がオブジェクト指向プログラミングに関して調べ、感じたことをそのまま書いているので、一部誤りがある可能性もあります。誤字、修正すべき箇所がありましたら、お手数ですがコメント等でご連絡していただければと思います。