#概要
この記事はオブジェクト指向にアレルギーを持っている人(私含め)に、できる限りわかりやすいように「オブジェクト指向プログラミングはこわくないよ」ということが目的の記事です。
私自身、プログラミング初心者のため、用語等の説明が違ったり、わかりにくい表現があるかもしれませんが、ご了承ください。
##この記事の対象者
1. オブジェクト指向プログラミングについて大雑把に知りたい
2. 初心者の書く記事がどのようなコンテンツか知りたい
3. 他人の書いたソースコードを読んでみたい
4. 1~3以外のその他の読者
#そもそもオブジェクト指向プログラミングって何ぞや?
##Wikipediaによると
オブジェクト指向プログラミング - Wikipedia
オブジェクト指向プログラミングとは
オブジェクト指向プログラミング(オブジェクトしこうプログラミング、英: object-oriented programming、略語:OOP)とは、互いに密接な関連性を持つデータとメソッドをひとつにまとめてオブジェクトとし、それぞれ異なる性質と役割を持たせたオブジェクトの様々な定義と、それらオブジェクトを相互に作用させる様々なプロセスの設定を通して、プログラム全体を構築するソフトウェア開発手法である。
オブジェクト指向とは
オブジェクト指向という用語自体は、計算機科学者アラン・ケイによって生み出されている。1962年公開の言語「Simula」の設計に印象を受けたケイが咄嗟に口にしたとされるこの造語は、彼が1972年から公開を始めた「Smalltalk」の言語設計を説明する中で初めて世間に発信された。なお、ケイが示したメッセージパッシングを中心にするオブジェクト指向は広く認知される事はなく、オブジェクトというプログラム概念を注目させるだけに留まっている。同時にケイの手から離れたオブジェクト指向は抽象データ型を中心にした解釈へと推移していき、1983年に計算機科学者ビャーネ・ストロヴストルップが公開した「C++」が契機になって、日本では一般にOOPの三大要素と呼ばれるカプセル化、継承、多態性といったパラダイムが確立されている。
##つまりどういうものなんや?
互いに密接な関係を持つデータとメソッドをひとまとまりにしたもの。
※11/19追記
車で例えると、車の車種、色など(データ)とアクセル、ブレーキ、ウィンカーなど(メソッド(操作))をひとまとまり(クラス)にしたもの。と言えそうです。
#オブジェクト指向プログラミング(OOP)
- ソースコードはPython3.9で書いています。
- 車を例にしてソースコードを書いています。
##クラスとインスタンス
###クラスとは
車を作る際の設計図に当たります。
以下、サンプルコードです。
class Car:
def __init__(self):
pass
###インスタンスとは
作った車そのもの(実体)に当たります。
以下、サンプルコードです。
class Car:
def __init__(self):
pass
car = Car() # インスタンス
##さて、車にはどんな情報、どんな部品、どんな機能があるでしょうか?
例えば、車種やナンバー、色、エンジン、タイヤetc...
そして、アクセルやブレーキなどもありますね。
これらの情報(の一部を)設計図に追加していきましょう。
以下、サンプルコードです。
class Car:
def __init__(self, name, color):
self.name = name # 車の名前
self.color = color # 車の色
def start_engine(self):
print("エンジンをかけました。")
def stop_engine(self):
print("エンジンをきりました。")
def accelerator(self):
print("アクセルを踏みました。")
def brake(self):
print("ブレーキを踏みました。")
mycar = Car("プリウス", "白") # インスタンス生成
mycar.start_engine() # エンジンをかけました。と表示される
mycar.accelerator() # アクセルを踏みました。と表示される
mycar.brake() # ブレーキを踏みました。と表示される
mycar.stop_engine() # エンジンをきりました。と表示される
ひとまず、これでおいておきましょう。
##OOP(オブジェクト指向プログラミング)の三大要素
先ほど、Wikipediaの引用にて少し出ていました。
- カプセル化
- 継承
- 多様性
###カプセル化
※11/19 一部表現等を変更しました。
プログラミングにおけるカプセル化(カプセルか、英: encapsulation)とは、データ(属性)とメソッド(手続き)を一つのオブジェクトにまとめ、その内容を隠蔽することを言う。カプセル化の概念は、D.L.パルナスの情報隠蔽(information hiding)の構成概念の一つとして見ることができる。
オブジェクト指向プログラミングにおけるオブジェクトは、クラスによる情報のカプセル化を行うことで作られる。
カプセル化では外部に公開したくない情報などを隠蔽します。
Pythonでは、アンダースコアを変数名(や関数名)の前につけることでカプセル化を行います。
ただし、Pythonではカプセル化は極力行わないほうが良いとされています。
アンダースコア1つの場合は、そのクラス内だけで使用されるべきもの、
アンダースコア2つの場合(ダンダーという)は、より使用を制限したものです。
呼び出し方がやや特殊になりますが、呼び出せないわけではありません。
また、複雑な処理を簡単に見せかける目的で使用する場合もあります。
その場合は、積極的に利用すべきです。
車で例えると、エンジンを始動させるためには内部でいろいろなこどが行われていますが、
運転手はそれを知っている必要がないのと一緒です。
以下、サンプルコードです。
class Car:
def __init__(self, name, color):
self.name = name # 車の名前
self.color = color # 車の色
self._age = "2020年式" # 年式
self.__engine = "ガソリンエンジン" # エンジンの種類
mycar = Car("プリウス", "白")
print(mycar._age) # 実行可能, 「2020年式」と表示される
mycar._age = "2019年式" # 代入も可能 ※すべきではない
print(mycar.__engine) # これではエラーが起こる
# AttributeError: 'Car' object has no attribute '__engine'
print(mycar._Car__engine) # 実行可能, 「ガソリンエンジン」と表示されるが、すべきではない
###継承
継承 (プログラミング) - Wikipedia
継承(けいしょう、英: inheritance、インヘリタンス)とはオブジェクト指向を構成する概念の一つである。あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる。(中略)
一般的に、BがAを継承する場合、"B is a A."(BはAの一種である)という意味的な関係(Is-a関係)が成り立つ。従って、同じふるまいを持つからと言って、意味的に無関係なクラス間に継承関係を持たせるのは適切でない場合が多い。
継承と類似の概念に「委譲」があるが、継承では一度定まった継承関係は通常変更されないのに対して、委譲対象は必要に応じて変更されうるものである。
Is-a関係を持つ継承とは階層が異なる概念として集約 (aggregation) とコンポジション集約 (composition) があるが、これはクラス間の関係がHas-aである包含関係であり、クラス間の関係は継承よりも疎である。
要約すると、is-a関係が成り立つものは継承の利用したほうが良いということです。
ただし、継承をむやみやたらに利用するのは控えるほうがよいです。
車で例えると、
スポーツカー is a 車や
特殊車両 is a 車などに当たります。
以下、サンプルコードになります。
Carクラスは先ほどと一緒です。
class Car:
def __init__(self, name, color):
...
...
class SportCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
class SpecialCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
継承されるクラスを親クラス、継承するクラスを子クラスといいます。
上記のサンプルコードでは、Carクラスが親クラス、SportCar, SpecialCarが子クラスにあたります。
子クラスのsuper().__init__(name, color)
では、親クラスの__init__
が呼ばれます。
###多様性(ポリモーフィズム)
ポリモーフィズム - Wikipedia
ポリモーフィズム(英: Polymorphism)とは、プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。
Wikipediaを見ても、言っていることがよくわからないと思いますので、サンプルコードを見てください。
以下サンプルコードになります。
class Car:
def __init__(self, name, color):
...
def accelerator(self):
print(f"アクセルを踏みました。({self.name})")
def brake(self):
print(f"ブレーキを踏みました。({self.name})")
class SportCar(Car):
...
class SpecialCar(Car):
...
cars = [Car("プリウス", "白"), SportCar("フェラーリ", "赤"), SpecialCar("救急車", "白")]
for car in cars:
car.accelerator()
car.brake()
アクセルを踏みました。(プリウス)
ブレーキを踏みました。(プリウス)
アクセルを踏みました。(フェラーリ)
ブレーキを踏みました。(フェラーリ)
アクセルを踏みました。(救急車)
ブレーキを踏みました。(救急車)
同じ名前のメソッドを用意することで、型(クラス)を意識せずに利用をすることが可能になります。
#一通りの説明は終わり
説明については、以上になります。
これからオブジェクト指向プログラミングらしいプログラムにソースコードを修正していきます。
元のソースコードはこちら
class Car:
def __init__(self, name, color):
self.name = name # 車の名前
self.color = color # 車の色
def start_engine(self):
print("エンジンをかけました。")
def stop_engine(self):
print("エンジンをきりました。")
def accelerator(self):
print("アクセルを踏みました。")
def brake(self):
print("ブレーキを踏みました。")
class SportCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
class SpecialCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
cars = [Car("プリウス", "白"), SportCar("フェラーリ", "赤"), SpecialCar("救急車", "白")]
for car in cars:
car.accelerator()
car.brake()
少し修正します。
車にスタートエンジン、ストップエンジンという機能はありません。
エンジンボタンが1つついてあるだけです。
なので…
start_engin(self)
とstop_engine(self)
を1つにまとめます。
また、エンジンがかかっている状態とエンジンがきれている状態を持たせます。
class Car:
def __init__(self, name, color):
self.name = name # 車の名前
self.color = color # 車の色
self._engine = False # エンジンがかかっているときはTrue, かかっていないときはFalse
def engine(self):
print(f"[{self.name}]", end=" ")
self._engine = not(self._engine)
if self._engine:
print("エンジンをかけました。")
else:
print("エンジンをきりました。")
def accelerator(self):
print(f"[{self.name}]", end=" ")
print("アクセルを踏みました。")
def brake(self):
print(f"[{self.name}]", end=" ")
print("ブレーキを踏みました。")
class SportCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
class SpecialCar(Car):
def __init__(self, name, color):
super().__init__(name, color)
cars = [Car("プリウス", "白"), SportCar("フェラーリ", "赤"), SpecialCar("救急車", "白")]
for car in cars:
car.engine()
car.accelerator()
car.brake()
car.engine()
[プリウス] エンジンをかけました。
[プリウス] アクセルを踏みました。
[プリウス] ブレーキを踏みました。
[プリウス] エンジンをきりました。
[フェラーリ] エンジンをかけました。
[フェラーリ] アクセルを踏みました。
[フェラーリ] ブレーキを踏みました。
[フェラーリ] エンジンをきりました。
[救急車] エンジンをかけました。
[救急車] アクセルを踏みました。
[救急車] ブレーキを踏みました。
[救急車] エンジンをきりました。
こんな感じでしょうか。
あまりいい例が思い浮かばなかったので、実感がわかりにくかったかもしれません。
大規模・大人数になればなるほどオブジェクト指向プログラミングが役に立つと思います。
今回はこれで終わります。
最後までお読みいただき、ありがとうございました。
間違いやアドバイス等があればコメントいただけると嬉しいです。