ヘルパー
SenseTalkオブジェクトは単独で存在する必要はありません。他のオブジェクトが一部または全部の動作を補助(helped
)することが可能です。例えば、誕生日に基づいてその人の年齢を計算するハンドラーを持つ人物オブジェクトを作成した場合、その人物オブジェクトは他のオブジェクトのヘルパーとして使用でき、それらも年齢を計算することが可能になります。そのためには、それら自身のハンドラーは不要です。
以下に、RexとSueという名前の他の二つのオブジェクトによってヘルプされるオブジェクトのサンプルスクリプトを示します:
sayHello -- 最初に友好的な態度で始めましょう!
properties
helpers: [Rex, Sue],
birthdate: "May 14, 1942",
end properties
on sayHello
put "Greetings! My age is " & calculateAge()
end sayHello
このオブジェクトのsayHello
ハンドラーはcalculateAge
という名前の関数を呼び出します。sayHello
ハンドラーが呼び出されると、それはcalculateAge()
を呼び出します。このオブジェクトにはcalculateAge関数ハンドラーがありませんが、もしヘルパーの一つ、例えばRexがそのような関数ハンドラーを持っているなら、そのハンドラーはこのオブジェクトの代わりに実行され、年齢を計算します。
Who is Me? What is This Object?
オブジェクトとそのヘルパーは密接に連携し、まるで一つのオブジェクトのように動作します(三銃士のスローガン、「一人のために全員、全員のために一人」を思い浮かべてください)。ヘルパー内のハンドラーが実行されるとき、それは元のオブジェクトのハンドラーであるかのように扱われます。このコンテキストでは、me
とmy
はヘルパーではなく、ヘルプされるオブジェクトを指します。
これは、ヘルパーのスクリプト内で実行される任意のステートメントがそのメッセージをヘルプされるオジェクトにターゲットし(そして間接的にヘルパーに対して)、ヘルパーがme
またはmy
を使用してプロパティにアクセスするとき、ヘルプされるオブジェクトのプロパティにアクセスすることを意味します。スクリプトが自身をターゲットにするか、または自身のプロパティにアクセスする必要がある場合、me
の代わりにthis object
という用語が使われることがあります。
ヘルパーのメッセージパス内の位 置
オブジェクトのヘルパーは、メッセージパス内でそのオブジェクトと密接に関連しています。オブジェクトがハンドラーを持っていないメッセージ(または取り扱ってからパスする)を受け取ると、そのメッセージは次にオブジェクトのヘルパーに行きます。ヘルパーがメッセージを取り扱わない場合のみ、メッセージはメッセージパス内の他のオブジェクト、たとえばbackScripts
内のオブジェクトにパスされます。
ヘルパーに設計されたオブジェクト
どのオブジェクトも、他の一つまたは複数のオブジェクトのヘルパーとなることができます。例えば、RexというオブジェクトがcalculateAge()
関数ハンドラーを持っていると仮定します。そして、同じcalculateAge()
機能が必要な別のオブジェクト、Lunaがあると仮定します。RexをLunaのヘルパーのリストに追加することで、Lunaはその能力を獲得します。しかし、Rexは他にもハンドラーを持っているかもしれません。それら全てがLunaの行動の一部として含まれることが望ましいわけではありません。Lunaが必要とする機能だけをどのように提供することができるでしょうか?
このような状況に対処する一つの良い方法は、複数の異なるオブジェクトに有用かもしれないその行動(ハンドラー)を分け出し、特にヘルパーとしての役割を果たすために設計された新しいオブジェクトを作成することです。例えば、calculateAge()
ハンドラーをRexからPersonオブジェクトに移動し、それからPersonをRexとLunaのヘルパーリストに追加するかもしれません。各オブジェクトは任意の数のヘルパーを持つことができますので、関連する行動のグループをヘルパーオブジェクトに分け出すことは非常に強力なツールとなります。それにより、異なるオブジェクトでその機能をさまざまな組み合わせで再利用することが可能となります。
ヘルパーとなるオブジェクトの設計
多くのヘルパーオブジェクトは非常にシンプルで、数個のハンドラーを持つスクリプトだけを持っています。例えば、Personオブジェクトは、私たちが話してきたcalculateAge()
関数のような単一のハンドラーで始まるかもしれません:
to calculateAge -- returns my current age in years
return (the date - my birthDate) / 365.25 days
end calculateAge
もっと私(ああ私!)について
ヘルパーについて理解するための非常に重要なことの一つは、ヘルパーのスクリプト中でme
とmy
という用語が使われるとき、それらはヘルパーとそのプロパティではなく、助けられるオブジェクトとそのプロパティを指すということです(必要に応じてヘルパーを指すためにthis object
を使用します)。これは通常、オブジェクトの一部として書かれたハンドラーは、別のオブジェクトを助けるときも、何の変更もなく正しく動作することを意味します。
他のオブジェクトに基づくオブジェクトの作成
一つのオブジェクトを他のオブジェクトのヘルパーとして使用し、最初のオブジェクトの行動を借りることは非常に簡単で、特別な努力を必要としないことがよくあります 。あなたがスクリプトの一部の機能を、主に他のオブジェクトのヘルパーとして機能するオブジェクトに分けることを決定したら、活用したい追加の機能がいくつかあるかもしれません。
このセクションの前半で、new object
表現を使用して単純なオブジェクトを作成する方法を見ました。new object
表現を使用する一般的な方法は、新しく作成するオブジェクトがどのようなタイプのものであるかを指定するために、そのオブジェクトが基になるものを提供することです。これは次の例で示されています:
put new Person with (name:"Elizabeth", age:14) into daughter
ここで、Personは新しく作成されるオブジェクトを定義するために使用されるオブジェクト("プロトタイプ"オブジェクトと呼ばれる)の 名前です。この例では、SenseTalkは"Person"という名前のオブジェクトを探し、そのオブジェクトに初期プロパティをパラメータとしてmakeNewObject
関数メッセージを送ります。これにより、プロトタイプオブジェクトは新しいオブジェクトがどのようなものになるかを完全に制御するチャンスを得ます。通常、新しいオブジェクトはプロトタイプオブジェクトそのものに基づいて作られ、プロトタイプオブジェクトをヘルパーとして持つことでその全ての行動を継承し、プロトタイプの通常のプロパティ(スクリプトとヘルパー以外)のコピーを受け取ります。それでは、これがどのように機能するかをもう少し詳しく見ていきましょう。
プロトタイプオブジェクトの役割
もしプロトタイプオブジェクトがmakeNewObject
関数を持っている場合、その関数は新しいオブジェクトを作成して返すべきです。もしmakeNewObject
メッセージを処理しない場合、SenseTalkの組み込みのmakeNewObject
関数が呼び出され、新しいオブジェクトを作成します(この例では、Personがヘルパーとなる)。そのオブジェクトのnew
式で提供されたすべてのプロパティを設定し(このケースでは"name"と"age")、すでに存在しないプロトタイプからの任意のプロパティを追加し、そのオブジェクトに"initialize"メッセージを送ります。したがって、組み込みの振る舞いの効果は、このmakeNewObject
関数と基本的に同じです:
to makeNewObject initialProperties
get new object with initialProperties helped by me
add properties of me to it
send "initialize" to it
return it
end makeNewObject
もし異なる振る舞いが必要な場合は、単にプロトタイプオブジェクトにカスタムmakeNewObject
関数を書きます。例えば、初期プロパティ値をいくつかデフォルトで設定したり、追加のヘルパーを含めたり、あるいは新しいオブジェクトを作るために別のプロトタイプオブジェクトを呼び出すこともあります。
デフォルトの振る舞いは、プロトタイプオブジェクトを助ける新しいオブジェクトを作成することであることに注意してください。そのため、任意のオブジェクトをプロトタイプオブジェクトとして使用することができ、それが新しいオブジェクトの主要なヘルパーとなります。
新しいオブジェクトのプロパティの初期化
プロトタイプから新しいオブジェクトが作成されると、デフォルトではプロトタイプオブジェクトの全ての振る舞いを継承し、そのプロパティのコピーも受け取ります。また、new
式で提供される任意のプロパティも受け取ります(これはプロトタイプからの対応するプロパティ値を上書きします)。プロトタイプオブジェクトは新しいオブジェクトに対して追加のプロパティを提供することもできます。これを行う一つの方法は、既に述べたように、カスタムmakeNewObject
関数を書くことですが、これはまれにしか必要ありません。よりシンプルで(そしてより良い)選択肢は、組み込みのmakeNewObject
関数によって新しいオブジェクトに送られる"initialize"メッセージを利用することです("initialize"メッセージはオブジェクトとそのヘルパーにのみ送られ、フルメッセージパスを通じては送られません)。以下に、いくつかのデフォルトのプロパティ値を提供するinitializeハンド ラの例を示します:
to initialize
add properties {time:"12:00", priority:"Normal"} to me
end initialize
もしこのハンドラが"May"という値を持つmonthプロパティを持つプロトタイプAppointmentオブジェクト内にあるなら、新しいappointmentは以下のように作成できます:
新しいAppointmentを{time:"8:30"}で作成してmeetingに入れる
これは、時間プロパティが"8:30"に設定され、優先度が"Normal"に設定された新しいオブジェクトを作成します。なぜなら、add properties
コマンドは既に存在するプロパティを置き換えないからです。したがって、new
式で提供された値は、このinitializeハンドラによって提供されたものより優先されます。新しいオブジェクトはまた、プロトタイプオブジェクトからコピーされた"May"という値を持つmonthプロパティも持つでしょう。
空のオブジェクトの作成
時々、プロトタイプから全てのプロパティのコピーを自動的に受け取る新しいオブジェクトを作成したくないことがあります。これを行うには、プロトタイプの前にempty
という言葉を使用します:
新しい空のAppointmentを{time:"8:30"}で作成してmeetingに入れる
ここでは、新しいオブジェクトはAppointmentによって補助され、そのtimeプロパティは"8:30"に、priorityプロパティは"Normal"に設定されます(initializeハンドラから)。プロトタイプオブジェクトから直接コピーされたmonthプロパティ(またはその他のプロパティ)を受け取ることはありません。
オブジェクトをテキストとして表示する
オブジェクトがテキストとして表示されるとき、デフォルトではそのキーと値はplistPrefix
、plistSuffix
、plistKeySeparator
、plistEntrySeparator
プロパティによって決定される形式で表示されます。これはListsとProperty Listsで説明されています。しかし、オブジェクトはスクリプトの一部としてasText
関数のハンドラを実装することで、好きな方法でテキスト文字列として自己を表現することができます。以下のようになります:
to handle asText
return "Part number " & my partNum
end asText
オブジェクトがasText
メッセージを処理しないが、asText
プロパティを持っている場合、そのプロパティの値がオブジェクトの文字列表現として使用されます。これはスクリプトを全く持っていないかもしれない一部のシンプルなオブジェクトにとって非常に便利です。
さ らに柔軟性を持たせるために、オブジェクトがasText
ハンドラもasText
プロパティも持っていないが、asTextFormat
プロパティを持っている場合、そのプロパティの値はmerge()関数によって評価され、オブジェクトのテキスト表現を提供します。これにより、オブジェクトの他のプロパティの値を組み合わせたよりダイナミックな解決策が提供されます:
set account to {type:"Savings", balance:1234.25,
asTextFormat:"[[my type]] Account: [[my balance]]"}
put account -- "Savings Account: 1234.25"
add 5050.50 to account's balance
put account -- "Savings Account: 6284.75"
オブジェクトの内容を確認する
スクリプトがcontains
またはis in
演算子を使用してオブジェクトが特定の値を含むかどうかを確認するとき、そのオブジェクト(またはそのヘルパーの1つ)は、任意の方法でテストを行う関数を実装することができます。これらの演算子がオブジェクトまたはプロパティリストに値の存在を確認するために使用されると、containsItem
関数メッセージがそのオブジェクトに送信されます。このメッセージのハンドラを実装すると、そのハンドラには2つのパラメータが渡されます。それは検索対象の値と、テストが大文字小文字を区別するべきかどうかを示すブール値で、そしてそれは指定された値がオブジェクト内に存在するかどうかを示すためにtrue
またはfalse
を返すべきです。
オブジェクトがcontainsItem
メッセージを処理しない場合、SenseTalkのその関数の実装が呼び出されます。組み込み関数の動作は、the objectContainsItemDefinition
グローバルプロパティを設定することで構成できます。このプロパティがAsTextContains
(デフォルトの振る舞い)に設定されている場合、containsItem
関数はオブジェクトのテキスト値(上述のように)が検索値を含む場合に真を返します。NestedValueContains
に設定されていると、演算 子はオブジェクトの値のそれぞれに適用され、その中のいずれかが検索値を含む場合、式は真を返します。最後に、KeyOrValueEquals
の設定は、検索値がオブジェクトのキーの1つまたはその値の1つと等しい場合に真を返します。
Advanced Topic: Early Helpers
オブジェクトのヘルパーは、オブジェクト自体がすでにハンドルしていない任意のメッセージをハンドルすることで、追加の振る舞いを提供します。オブジェクトは、自分自身のハンドラを提供することで、ヘルパーによって提供された任意のハンドラをオーバーライドできます。たまに、オブジェクト自体が定義した振る舞いをオーバーライドするヘルパーを持つオブジェクトを提供できるようにすることが有用であるかもしれません。Early Helpersはこれを実現します。オブジェクトが受け取る任意のメッセージは、最初にオブジェクトのearly helpersに送られ、次にオブジェクト自体に、そして最後にそのヘルパーに送られます。
オブジェクトにヘルパーを割り当てる
既に、オブジェクトが最初に作成されるときにヘルパーがオブジェクトに割り当てられる方法はいくつか見てきました:スクリプトファイルのプロパティ宣言にヘルパーのリストを含めること、プロパティリストやnew object
表現に続く"helped by"節にそれらをリスト化すること、または最初のヘルパーになるプロトタイプオブジェクトからオブジェクトを作成すること。
オブジェクトのヘルパーのリスト - そして早期ヘルパーのリスト - もまた、スクリプトによっていつでも動的に変更できます。単にhelpers
またはearly helpers
プロパティにアクセスし、必要に応じてオブジェクトを挿入または削除します:
FritzのhelpersにCleverTricksを挿入する