注目の投稿

技術Note一覧

このブログのメインコンテンツである技術Noteの一覧

2016/12/23

オブジェクト指向とはなにか

1.意見

オブジェクト指向プログラミング(OOP) の意味としては2段階あると思う.
1. 実装とインターフェースの分離による仕様変更に強いプログラミング
2. ユーザとのインタラクションが取りやすい,かつ実行時エラーの起きにくいソフトを作るためのプログラミング

1. はライトユーザーが感じる恩恵.別にクラスを使わなくても実現できる.
2. はソフトを売り物にする人たちが感じる(と思われる)恩恵.クラスがないと実現できない.

OOPの考え方を実現する機能=クラス
クラスの役割は3つ
実装とインターフェースの分離をしやすくする仕組みの提供(カプセル化)
コンパイル時の静的型チェックを緩くする仕組みの提供(継承)
・チェックを緩くしたことにより起こる得る実行時エラーを回避する仕組みの提供(ポリモーフィズム)

カプセル化については一般論と同じ意見なので省略


2.コンパイル時の静的型チェックを緩くする仕組み

クラス大きな特徴に,もしクラスa,bがクラスAを継承していれば,A型の変数にa型のインスタンスでもb型のインスタンスでも代入できるというものがある.
普通だったらString型にDouble型を代入しようとするとコンパイルで怒られる(多分)
このルールによって,ユーザとのインタラクションが取りやすいソフトが作れる.

具体例 (a)
パワポ上で丸描いて,四角描いて,三角描いて,最後に全部の図を一斉に縮小したいとする.
このとき,パワポ内部のプログラムを想像すると,circle 型、rectangle型、triangle型みたいな変数が定義されているはず. であれば,一斉に縮小するための関数(resize()と呼ぶ)はcircle型,rectangle型,triangle型の変数を引数にとるような関数として定義されている.
resize( circle c , rectangle r , triangle t){}

(b)
ここで,さらにバツ印(cross型)も追加で描いたとする.
このとき,先程定義したresizeはバツ印の縮小には使えない.
なぜなら,resize()にcross型変数を渡す動作を記述しても,型が違うためコンパイルがそもそも通らないから.
ということは,circle型,rectangle型,triangle型,cross型の4つを引数にとる関数resize2()が定義されている必要がある.
resize2( circle c , rectangle r , triangle t , cross cr){}

同様に,丸と三角を同時に縮小するための関数も必要だろうし,四角とバツと丸を同時に縮小する関数も必要だろう.
結局,ありえる全ての組み合わせに対して専用の関数がパワポ内に定義されていることになる. もし自分が開発側だったらこれはとてもめんどくさい.

(c)
そんなめんどくさいことをやっているはずはない.
実際には,図形の組み合わせを全列挙するのではなく,「図形一般を縮小する」という関数を定義している(多分).
ここでやっとクラスの出番になる.
クラスではこういうとき,図形一般を表すdiagram型という型を考える.
そして,クラスに関するルールとして、diagram型にはcircle型,rectangle型,triangle型,cross型が代入できると定める.(これはクラスの非常に大きな特徴である.普通だったらString型にDouble型を代入しようとするとコンパイルで怒られる)
このルールの下で複数のdiagram型(=diagram型の配列)を引数にとって図形を縮めるという関数を作ることができるようになる.
super_resize( diagram d[] ){}

この関数により,どんな図形に対しても単一の関数で一斉縮小が実現できる.

まとめ
継承の活用により,ユーザがどんな種類の図形をいくつ作っても応答することが可能になる.つまり,ユーザができる入力(図形を描く)の自由度が高く,インタラクションが取りやすいソフトが作れる.
このご利益の背景には,コンパイル時に宣言型と代入型が異なっても許されるというクラスルール=「コンパイル時の静的型チェックを緩くする仕組み」がある.


3.チェックを緩くしたことにより起こる得る実行時エラーを回避する仕組み


(a)
super_resize()による図形縮小が二次元的に縮小するという動作を記述していたとする.
この場合,super_resize()によって一次元の線(line型の変数とする)を縮小しようとするとエラーが起きる.
しかも不幸なことに,line型変数はdiagram型に代入できるからコンパイル時にはエラーがでない.
だから,実行時に突然プログラムが落ちて作業保存されず,しかもプログラムのどの行のエラーなのか不明,という挙動がおきる

(b) でもクラスの機能を使えば大丈夫.
実は,クラスでは全く同名の関数super_resize()を複数定義できるというルールがある.
一つはもちろん普通の縮小動作.もう一つは何もしないという動作を記述する.
以下省略.多態性・ポリモーフィズムの話がしたかった.

(c : まとめ)
同じ名前のsuper_resize()でもline型とその他型で挙動変えられる.だからエラー回避処理を上手く書くことができる.こういう実行時エラーを回避する仕組みがある.


4.総まとめ

super_resize()関数により,ユーザがどんな種類の図形をいくつ作っても応答することが可能になる.つまり,ユーザができる入力(図形を描く)の自由度が高く,インタラクションが取りやすいソフトが作れる.

このメリットは,コンパイル時の静的型チェックを緩くする仕組みによって生じている.
一方で,型チェックが緩いから実行時にとんでもねえエラーが発生するリスクも大きくなる.
そこで,エラー回避処理を上手く書くことができるような仕組みによって,実行時エラーを回避しやすくしている.

結果,クラスの機能を使うと,ユーザとのインタラクションが取りやすい,かつ実行時エラーの起きにくいソフトが作れるようになる.
このメリットを活用しようとうまーくコーディングするスタイルがオブジェクト指向プログラミングなんじゃないかなと思いました.


5.参考

http://qiita.com/hirokidaichi/items/591ad96ab12938878fe1?utm_content=bufferb7e80&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer

「カプセル化でわざわざ内部データを隠すのはなぜ? 構造体と、構造体を引数にとるメソッドを用意してデータへのインターフェースを提供するだけじゃだめか?」という点に触れている