メインコンテンツまでスキップ

ヘルパー

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?

オブジェクトとそのヘルパーは密接に連携し、まるで一つのオブジェクトのように動作します(三銃士のスローガン、「一人のために全員、全員のために一人」を思い浮かべてください)。ヘルパー内のハンドラーが実行されるとき、それは元のオブジェクトのハンドラーであるかのように扱われます。このコンテキストでは、memyはヘルパーではなく、ヘルプされるオブジェクトを指します。

これは、ヘルパーのスクリプト内で実行される任意のステートメントがそのメッセージをヘルプされるオジェクトにターゲットし(そして間接的にヘルパーに対して)、ヘルパーが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

もっと私(ああ私!)について

ヘルパーについて理解するための非常に重要なことの一つは、ヘルパーのスクリプト中でmemyという用語が使われるとき、それらはヘルパーとそのプロパティではなく、助けられるオブジェクトとそのプロパティを指すということです(必要に応じてヘルパーを指すために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関数と基本的に同じです:

makeNewObject初期プロパティへ
私を助ける初期プロパティを持つ新しいオブジェクトを取得
私のプロパティをそれに追加
"initialize"をそれに送る
それを返す
makeNewObject終了

もし異なる振る舞いが必要な場合は、単にプロトタイプオブジェクトにカスタムmakeNewObject関数を書きます。例えば、初期プロパティ値をいくつかデフォルトで設定したり、追加のヘルパーを含めたり、あるいは新しいオブジェクトを作るために別のプロトタイプオブジェクトを呼び出すこともあります。

デフォルトの振る舞いは、プロトタイプオブジェクトを助ける新しいオブジェクトを作成することであることに注意してください。そのため、任意のオブジェクトをプロトタイプオブジェクトとして使用することができ、それが新しいオブジェクトの主要なヘルパーとなります。

新しいオブジェクトのプロパティの初期化

プロトタイプから新しいオブジェクトが作成されると、デフォルトではプロトタイプオブジェクトの全ての振る舞いを継承し、そのプロパティのコピーも受け取ります。また、new式で提供される任意のプロパティも受け取ります(これはプロトタイプからの対応するプロパティ値を上書きします)。プロトタイプオブジェクトは新しいオブジェクトに対して追加のプロパティを提供することもできます。これを行う一つの方法は、既に述べたように、カスタムmakeNewObject関数を書くことですが、これはまれにしか必要ありません。よりシンプルで(そしてより良い)選択肢は、組み込みのmakeNewObject関数によって新しいオブジェクトに送られる"initialize"メッセージを利用することです("initialize"メッセージはオブジェクトとそのヘルパーにのみ送られ、フルメッセージパスを通じては送られません)。以下に、いくつかのデフォルトのプロパティ値を提供するinitializeハンドラの例を示します:

initializeへ
プロパティ{time:"12:00", priority:"Normal"}を私に追加
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プロパティ(またはその他のプロパティ)を受け取ることはありません。

オブジェクトをテキストとして表示する

オブジェクトがテキストとして表示されるとき、デフォルトではそのキーと値はplistPrefixplistSuffixplistKeySeparatorplistEntrySeparatorプロパティによって決定される形式で表示されます。これはListsProperty 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を挿入する