主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

JavaScript: オブジェクト指向番外 なぜオブジェクト指向の説明はクラスに縛られるのか

                
その1 - そもそもオブジェクトとはなんぞ
その2 - "コンストラクタ"を使うと類似オブジェクトを作るのが便利
その3 - "プロトタイプ"を使うとオブジェクト作成が合理的になる
番外編 - クラスの概念に縛られた巷のオブジェクト指向の説明

”JavaScrpt: 簡単なスクリプト(コード)で理解する「オブジェクト指向」”というタイトルで、記事を書いている。
これらを書き始めた発端は、巷のオブジェクト指向の説明にわかりづらいのが多かったから。そしてちょっとコアにすべき部分がずれてるんじゃないかと思う説明記事が簡単に目に入るところにあったから。
 説明がわかりにくい記事では、比喩の乱用や唐突なクラスの説明があった。比喩の乱用に関しては、スクリプトベースの説明をカウンターとして書いている。で、JavaScriptにクラスはないのに、クラスをベースにオブジェクト指向を説明した記事が多いのはなぜかと考えたところはここにまとめておく。JavaScriptからオブジェクト指向に入った人は、他言語にいく予定が直近になければそんなクラスベースの記事は後回しにしたほうがいい。

 クラスベースでオブジェクト指向の概念が説明された記事はわりと簡単に見つけられる。たとえばMozillaなんかも「オブジェクト指向JavaScript入門」と銘打ってそんな記事を書いている。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript
 思うに↑のような記事は、クラスベースの他言語でオブジェクト指向を学んできた人に向けて書かれた記事だ。はじめて学ぶオブジェクト指向言語がJavaScriptならば、そういう記事を最初に読むのはやめておいたほうがいい。そこではクラスを軸にオブジェクト指向を説明しているのに、別のところにある記事を読むとJavaScriptにはクラスがないという事実が書かれていて混乱をきたすから。
 クラスはオブジェクト指向の本質ではない。だからクラスのないJavaScriptでもオブジェクト指向言語であることができる。じゃあなんでオブジェクト指向の話をしようとすると、クラスの説明から始める人がいるのだろう。それはたぶん、クラスベースのオブジェクト指向言語の多くが、クラスを書いてそのインスタンスとしてオブジェクトを得るのが基本だから。

 ぼくの知っている言語はそんなに多くないが、知る中でPythonとC#はオブジェクトを作るのにクラスを書くのが基本だ。ためしにストップウォッチ機能を持つオブジェクトを書いてみる。そのオブジェクトが持つメソッドは、計測開始、経過時間取得、計測終了の3つで実装する。計測開始をしていないのに経過時間取得をしようとすると、Noneを、つまりなにも返ってこないようにしておく。
# 突貫で書いたので命名規則がPythonの流儀に従っていない
import time

class Stopwatch:
_t = None

def start(self):
self._t = time.time()

def getCurrentTime(self):
diff = None
if self._t:
diff = time.time() - self._t
return diff

def stop(self):
self._t = None

stopwatch = Stopwatch()

こんなスクリプトを書くことで、ストップウォッチ機能を持つオブジェクトを作ることができる。基本的にクラスには実体がないので、オブジェクトを得るため最後にインスタンスを作っている。ここまでして初めてオブジェクトが得られる。オブジェクトを初めて得るためには、クラスを知っておくことが避けて通れない。だからオブジェクト指向の説明において、まずクラスを説明してしまうものが多いのだろう。

 しつこいんだけど、クラスはオブジェクト指向の本質ではない。クラスを使えば同一機構を持つオブジェクト、類似機構を持つオブジェクトを簡単に作れるけど、それはオブジェクト指向を推進する機能であって、オブジェクト指向の本質とは違う。変数も関数も持つことができ、なおかつ自分の状態を保持しておけるものがあって、そこに特定の機能を構成するための変数、関数をまとめてしまおうというのがオブジェクト指向。実はJavaScriptはそれがクラスなしに簡単にできてしまう。
 JavaScriptでのもっとも簡単なオブジェクトの定義の仕方は、"var foo = {};"と書くだけである。これで変数fooはオブジェクトとなった。あとは必要なプロパティやメソッドをここに追加していけばいい。クラスを経由してオブジェクトを作ることなく、ただまっすぐにオブジェクトを作ることができる。
 ためしにPythonで書いたのと同様のストップウォッチ機能を持つオブジェクトを作ってみる。
var stopwatch = {};

stopwatch.time = null;

stopwatch.start = function()
{
this.time = Date.now();
};

stopwatch.getCurrentTime = function()
{
var diff = null;
if (this.time)
{
diff = (Date.now() - this.time) / 1000.0;
}
return diff;
};

stopwatch.stop = function()
{
this.time = null;
};

ストップウォッチオブジェクト完成。クラスを使わずにオブジェクトを得られた。JavaScriptってここがクール。

 オブジェクト指向の本質はクラスとは別のところにある。だけどクラスベースのオブジェクト指向言語でオブジェクト指向を学ぼうとすると、クラスを使わないとオブジェクトが得られないせいかクラスの説明が初めに来て、しまいには継承とかまで説明しちゃってる。そしてそのクラスベースでのオブジェクト指向の説明が、JavaScriptにまで伝染してきている。だけど初級者がいきなりクラスだの継承だのインスタンスだのの専門用語の嵐をかいくぐってオブジェクト指向を理解するのは難しい。クラスを経由せずにオブジェクトを作れるJavaScriptは捨てたもんじゃないんではと思う。


参考
オブジェクトは難しくない。難しいのはクラス
オブジェクト指向 【 object oriented 】 OO


おまけ
 クラスベースのオブジェクト指向言語では、クラスを書いてそのインスタンスを作らなければ、オブジェクトは得られないと書いた。Pythonにおいてはたぶんそれはウソ。根性でクラスのインスタンス化という手順を避けて、オブジェクトを作れるんじゃないだろか。そこをおまけとして少し。
 それぞれ異なった方法でストップウォッチオブジェクトを作ってみた。そのスクリプトを下に。
import time

# define test
def test(stopwatchObj):
# test 1: before start
t = stopwatchObj.getCurrentTime()
if t:
print "test 1 failed"
else:
print "passed test 1"

# test 2: done correct measure?
stopwatchObj.start()
time.sleep(0.3)
t = stopwatchObj.getCurrentTime()
stopwatchObj.stop()
if not (0.299 <= t < 0.301):
print "test 2.1 failed"

else:
print "passed test 2.1"

stopwatchObj.start()
time.sleep(0.6)
t = stopwatchObj.getCurrentTime()
stopwatchObj.stop()
if not (0.599 <= t < 0.601):
print "test 2.2 failed"
else:
print "passed test 2.2"

# test 3: stopwatch is stopping?
t = stopwatchObj.getCurrentTime()
if t:
print "test 3 failed"
else:
print "passed test 3"

def test_(stopwatchObj):
# test 1: before start
t = stopwatchObj["getCurrentTime"]()
if t:
print "test 1 failed"
else:
print "passed test 1"

# test 2: done correct measure?
stopwatchObj["start"]()
time.sleep(0.3)
t = stopwatchObj["getCurrentTime"]()
stopwatchObj["stop"]()
if not (0.299 <= t < 0.301):
print "test 2.1 failed"

else:
print "passed test 2.1"

stopwatchObj["start"]()
time.sleep(0.6)
t = stopwatchObj["getCurrentTime"]()
stopwatchObj["stop"]()
if not (0.599 <= t < 0.601):
print "test 2.2 failed"
else:
print "passed test 2.2"

# test 3: stopwatch is stopping?
t = stopwatchObj["getCurrentTime"]()
if t:
print "test 3 failed"
else:
print "passed test 3"

################################
# way 1
class Stopwatch1:
_t = None

def start(self):
self._t = time.time()

def getCurrentTime(self):
diff = None
if self._t:
diff = time.time() - self._t
return diff

def stop(self):
self._t = None

stopwatch1 = Stopwatch1()

################################
# way 2
class stopwatch2:
_t = None

@classmethod
def start(self):
self._t = time.time()

@classmethod
def getCurrentTime(self):
diff = None
if self._t:
diff = time.time() - self._t
return diff

@classmethod
def stop(self):
self._t = None

################################
# way 3
import sys,imp

stopwatch_string = """import time
_t = None
def start():
global _t
_t = time.time()

def getCurrentTime():
global _t
diff = None
if _t:
diff = time.time() - _t
return diff

def stop():
global _t
_t = None
"""

stopwatch3 = imp.new_module('stopwatch')
exec stopwatch_string in stopwatch3.__dict__


################################
# way 4
def Stopwatch4():
t = [None]
def start():
t[0] = time.time()

def getCurrentTime():
diff = None
if t[0]:
diff = time.time() - t[0]
return diff

def stop():
t[0] = None

methods = {"start":start, "getCurrentTime":getCurrentTime, "stop":stop}
return methods

stopwatch4 = Stopwatch4()

##########################
# tests
print "case 1"
test(stopwatch1)

print "case 2"
test(stopwatch2)

print "case 3"
test(stopwatch3)

print "case 4"
test_(stopwatch4)


 一番目の方法がまっとうで初歩的な方法だ。クラスを作ってそのインスタンスとしてオブジェクトを得ている。
 クラスにこなれてきた人なら二番目のように書くかもしれない。クラスメソッドはインスタンスなしに呼び出すことができ、なおかつクラスメンバにアクセスできる。オブジェクトを得るために必要なインスタンスの作成を回避している。
 三番目はモジュールを器用に利用したやり方。Pythonのクラスメソッドを公式ドキュメントで調べていたら、"しかし、静的メソッドの効果を得るもっと簡単な方法は、単にモジュールレベル関数を使うことです。(中略)。モジュールあたりに一つのクラスを定義するように (あるいはクラス組織を厳密に関連させるように) コードが構成されているなら、これで必要なカプセル化ができます"というヒントがあったのでやってみた。ただしこのオブジェクトの入手方法はちょっと手間。Pythonでは基本的に、モジュールを分けるには新しいファイルを用意する必要があるから。今回のスクリプトのように変な書き方をすれば新しいファイルは用意しなくても大丈夫だが、やっぱり新しいモジュールとして切り分けるのは手間。まだクラスを定義してインスタンスを作った方がマシ。だけどクラスを書かずにオブジェクトを得られた。 参考
 四番目はクロージャと辞書型の組み合わせ。これをオブジェクトと呼んでいいのかと思うけど、自分で必要な変数も関数もそろえていて、必要な命令を下すだけで、目的の処理を実行してくれる。オブジェクトのを十分に果たしているはず。メソッドの呼び方が"stopwatch4["start"]()", "stopwatch4["stop"]()"というふうになっていて一風変わっているが、ストップウォッチとしての機能は十分に果たしてくれる。クラスは書かずにオブジェクトを得られた。
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。