属性とハンドラの継承

オブジェクト作りを楽に

 既に似たようなオブジェクトがあるのならば、そのオブジェクトを元にして新しいスクリプトオブジェクトを作りたいと考えるのは自然なことです。
 AppleScriptでは、継承(inhelitance)という仕組みを用意して、そのような要望に応えています。

 アプリケーションの用語説明のところで、既に継承という言葉は出てきています。
 継承元のオブジェクトの属性がそのまま使えるようになる仕組みでした。そうすることで、同じような仕組みを何度も書く必要がなくなって、用語も統一され覚えやすくなっているわけです。

 今回は、その継承をスクリプトオブジェクトで行います。
 継承を行うことによって、親で作った属性やハンドラを受け継ぐことができます。

 また、tellによる指定ではなく、継承を利用することでアプリケーションを使うといった、他の言語では見かけない使い方もできます。これなどはAppleScriptらしい仕組みと言えるでしょう。

継承の書き方

 継承は既にあるオブジェクトをテンプレートとして、新しいオブジェクトを作る仕組みと考えることもでき、書類の「ひな形」とも似ています。

 継承元のオブジェクトを「親オブジェクト」ともいいます。
 親から見て継承を行っているオブジェクトを「子オブジェクト」といったりもします(注1)。

 親オブジェクトはpropaty文を使って設定します。書式は次の通り。

property parent : オブジェクト

 属性に指定できる名前のうち、parentだけは特別な意味を持っていて、親オブジェクトを指定します。
 また、親となるスクリプトオブジェクトは、必ず子オブジェクトの前に書いておく必要があります。

 では、親オブジェクトを指定して、継承を行ってみましょう。
 次のスクリプトでは、log命令を使っているので、エディタの[制御-イベントログを開く]メニューを選び、ログを開いておきます。

script ParentObj
	property prop_ : "親"
	on run
		log prop_ & "オブジェクトです"
	end run
end script

script ChildObj
	property parent : ParentObj
	property prop_ : "子"
end script

run of ChildObj

 ログには(*親オブジェクトです*)と表示されたかと思います。
 子の方で同じ名前の属性を指定してもそれは無視されて、実行するハンドラのあるスクリプトオブジェクト(この場合ParentObj)の属性が使われます。

ハンドラの継承

 親オブジェクトで定義されているハンドラを、子オブジェクトで再定義することもできます。
 その場合、子オブジェクトのハンドラの方が、親オブジェクトより優先されます。

 また、再定義したハンドラから、親オブジェクトにあるハンドラに処理を渡すこともできます。
 これには、continue文を使います。continue文は「ハンドラで受け止める」のquitハンドラのところでも説明しました。
 あの時の親オブジェクトはアプレットです。

 では、次のスクリプトを実行して、動作を確認してみましょう。

script ParentObj
	on handlerA()
		log "A_親"
	end handlerA
	
	on handlerB()
		log "B_親"
	end handlerB

	on handlerC()
		log "C_親"
	end handlerC
end script

script ChildObj
	property parent : ParentObj
	
	on handlerB()
		log "B_子"
	end handlerB
	
	on handlerC()
		log "C_子"
		continue handlerC()
	end handlerC
end script

handlerA() of ChildObj
handlerB() of ChildObj
handlerC() of ChildObj

 すべて、子オブジェクト(ChildObj)に対してハンドラを呼んでいます。
 実行すると、ログには順に(*A_親*)(*B_子*)(*C_子*)(*C_親*)と表示されたかと思います。

  • Aは子に定義が無いので、親にメッセージが渡されて親のハンドラが実行されています。
  • Bは両方に定義があるので、まず子のハンドラが実行されていますが、親のハンドラは実行されません。
  • Cは両方にハンドラがあるので、まず子が、そしてcontinue文によって親のハンドラが実行されます。

 この辺りの仕組みをきちんと理解しておかないと、継承を利用して楽するどころか、よけいややこしくすることになりますから、十分テストを繰り返して継承に親しんでおくと良いでしょう。

スクリプト属性を複製して利用

 スクリプトオブジェクトの属性は、setで変数に代入した場合には共有され、copyで変数に代入した場合は複製されて共有されません。
 具体的には次のスクリプトを見て下さい。

script ParentObj
	property prop_ : "初期設定"
end script

script ChildObj
	property parent : ParentObj
end script

prop_ of ChildObj
set setChild to ChildObj
copy ChildObj to copyChild

-- ここで変更しているのは、setChildでもcopyChildでもなくChildObjなのに注目
set prop_ of ChildObj to "変更"

log "setChild:" & prop_ of setChild
log "copyChild:" & prop_ of copyChild

 ログには、(*setChild:変更*)、(*copyChild:初期設定*)と表示されます。

 二回目の実行では、両方とも"変更"になります。
スクリプト属性を設定しよう」で書いたように、属性の変更が記録されているからです。

 スクリプトオブジェクトを変数に代入する場合、いくつかの属性を使い分けたい場合が多いでしょうから、setで代入しては属性が共有されてあまり意味がありません。copyで代入するようにしましょう。

 また、属性はcopyで複製した時点での値が入ります。スクリプトオブジェクトの初期値ではありません。
 それから、copy文の場合はsetと代入元と代入先が前後逆になりますから注意してください(注1)。

特別な継承元

 スクリプト内に定義したスクリプトオブジェクトの親は、定義の書いてあるスクリプトとなります。
 次のスクリプトを実行すると、parentで指定していないのに、continueの先が地のxハンドラになっていることが確認できると思います。

x() of ScriptObj

on x()
	log "地のスクリプト"
end x

script ScriptObj
	on x()
		log "スクリプトオブジェクト"
		continue x()
	end x
end script

 何も指定していない場合のスクリプトの親オブジェクトは、通常スクリプトを実行しているアプリケーションです。
 次のスクリプトをエディタで実行すると、選択したファイルがエディタで開かれます。

open (choose file)

 アプレットの場合、アプレットが親オブジェクトになります。
 エディタでテストしている時とアプレットに保存した場合とで、parentが異なるわけです。

 エディタの上部に、この時の対象アプリケーションを変更できるポップアップメニューがあります。
 これを切り替えると、対象アプリケーションのtellブロックでスクリプト全体を囲っているような状態になります。
 また、一度保存したアプリケーション(アプレット)は、[スクリプト]-[アプリケーションを実行]で、アプリケーションとして実行できます。

 parentの指定を変えると、実行中のアプリケーションではなく、他のアプリケーションを継承元に指定することもできます。

property parent : application "Finder"

 ただし、この場合はメッセージの継承元が指定したアプリケーションに変わっただけで、tell無しで用語が使えるようになるわけではありません。
 アプリケーション独自の用語を利用したい時は、通常どおりtellで指定する必要があります(注1)。
 openは、AppleScript自身やOSAXで定義されている用語なので使用できるわけです。

 parentを変えた場合も、current applicationという用語を使えば、現在のアプリケーションを指定することができます。
 次のスクリプトでは、parentをFinderに変えていますが、current applicationをtellで指定することで、openメッセージを現在のアプリケーション(例えばScript Editor)に送ることができます。

property parent : application "Finder"

tell current application to open (choose file)

 スクリプト付加可能なアプリケーションでは、地のスクリプトのメッセージの送り先が、そのアプリケーション自身になっている場合がありますが、そのような場合も、parentの指定で、メッセージの送り先を変えることができます。

 例えば、次のスクリプトをFileMakerに書いた場合、マウスのクリックで終了するのは、FileMakerではなくFinderになります。

property parent : application "Finder"
quit

スクリプトオブジェクトを明示する

 たいていの場合、暗黙に指定されるオブジェクトを利用していいのですが、メッセージの送り先にきちんと、親や現在のオブジェクトを指定したい場合もあります。
 そのような場合、現在のスクリプトオブジェクトは、前にも書いたようにmeを、そして親を指定するにはparentを使います。

script ParentObj
	property prop_ : "親"
	on run
		log (prop_ of me) & "オブジェクトです"
	end run
end script

script ChildObj
	property parent : ParentObj
	property prop_ : "子"
	
	on x()
		log (prop_ of me) & "オブジェクトです"
	end x
	
	on y()
		log (prop_ of parent) & "オブジェクトです"
	end y
end script

x() of ChildObj
y() of ChildObj
run of ChildObj
run of ParentObj

 ログには(*子オブジェクトです*)(*親オブジェクトです*)(*子オブジェクトです*)(*親オブジェクトです*)と順に表示されたかと思います。
 特に、meが最初に呼ばれたスクリプトオブジェクトを記憶しているということに注目して下さい。

処理を丸投げする

 継承は、作業を省力化できて便利ではあるのですが、仕組みが少々複雑で、思わぬ処理を行ってしまうことがあります。
 またAppleScriptでは、継承して欲しくない属性やハンドラを制限する仕組みが用意されていないので、事はより面倒なことになってしまいます。

 そこで、継承を使わずに処理を別のスクリプトオブジェクトに丸投げしてしまうことが考えられます。

script UtilityObj
	property prop_ : "ユーティリティー"
	on run
		log prop_ & "オブジェクトです"
	end run
end script

script UserObj
	property prop_ : "利用者"
	on run
		run of UtilityObj
	end run
end script

run of UserObj

 要するに、他のスクリプトオブジェクトのハンドラを呼んでいるだけです。
 これは、他のオブジェクト指向言語でもわりと行われていることで、継承よりも利点が多い場面も少なくありません。

 例えば継承では一つの親しか持てませんが、処理を丸投げすることで、いくつものオブジェクトの性質を持ったオブジェクトを作ることもできます。

 このように他のスクリプトオブジェクトのハンドラを呼ぶ時も、継承した場合に似せて、近い処理するハンドラは同じ名前にしておくことが大切です。
 ハンドラの名前の共通化を進めておけば、スクリプトオブジェクトを利用する時に、用意されているハンドラの予想が可能になり、スクリプトの製作がスムーズになります。

makeハンドラを作る

 スクリプトオブジェクトを沢山作って利用したい場合、いちいちcopy命令を使って変数に代入するのは格好よくないので、スクリプトオブジェクトを作るハンドラを作ってみましょう。

 ハンドラの中に、スクリプトオブジェクトの定義を書いた場合、そのハンドラの結果として、定義したスクリプトオブジェクトが返されます。
 また、引数を渡して、属性を初期化することもできます。具体的には、次のスクリプトを見て下さい。

set objList to {}
repeat with i from 1 to 20
	set end of objList to makeScriptObject("ぷろぱてぃ" & i)
end repeat
getProp() of item 1 of objList
--> "ぷろぱてぃ1"

on makeScriptObject(theProp)
	script ScriptObject
		property prop_ : theProp
		on getProp()
			return prop_
		end getProp
	end script
end makeScriptObject

 ハンドラの中に含まれているスクリプトオブジェクトは、ハンドラが実行された時に属性を初期化します。
 ですから、属性がファイルに保存されたりもしません。
 また、ハンドラが実行される度に、新しい別のスクリプトオブジェクトが作られて結果に返ります。
 それぞれのスクリプトオブジェクトで属性の値は共有されません。

 詳しくはTipsの「スクリプトオブジェクトの定義」に書いていますので、そちらの方も参照して下さい。

【今回のまとめ】

スクリプトオブジェクトの継承は、

  • オブジェクトの性質を引き継ぐ仕組み
  • property parentに設定するだけ
  • メッセージが、親に引き継がれる

他にスクリプトオブジェクトを便利に使うには、

  • 継承を使わずに丸投げ
  • setとcopyの違いに注意
  • ハンドラ呼び出しで作るとcopyいらず