目次


Iris Today Archives

ロータス スクリプトのオブジェクト指向特性を使う

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Iris Today Archives

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Iris Today Archives

このシリーズの続きに乞うご期待。

しかし、もしあなたが私の知る大抵のノーツ/ドミノ開発者と同じならば、あなたは、ドミノ・デザイナーがあなたの設計要素のために作成する(QueryOpen や QuerySave のような)ビルト・イン・サブルーチンの中で、これらのオブジェクトを使用するためのコード、そしてあなたの独自のサブルーチンや関数を書いているでしょう。これらは典型的で、ロータス スクリプトの最上級の開発者においてさえも見受けられます。しかし OO を利用すれば、あなたのアプリケーションの改良のために、より多くのことができるようになるのです。

あなたは、5つ、または6つのバージョンの同じ関数が、色々な箇所に置かれているノーツ・データベースをご覧になったことがありますか?さらにひどくなりますが、あなたはロータス スクリプトにおけるある関数の問題を解決して、他の場所でも同じ作業をしなくてはならないと気付かされたことはありますか?もしそうならば、あなた独自のクラスを定義することの利点に気付いていることでしょう。クラスは、関連するコードとデータを結びつけるのに便利です。「カプセル化」とも呼ばれる方法ですが、これによりコード内の更新が必要なすべての箇所をしらみつぶしに探し出す必要なく、プログラムの実行に様々な変更を加えることができます。

ロータス スクリプトにおけるオブジェクト指向特性が、そうできるほど頻繁には利用されていないのは、そこまで驚くことではありません。ほとんどの本や雑誌の記事では、この話題に関してえてして軽く取り上げるだけに過ぎないのですから。そしてまた、OO 理論について多くのことを学ばなくてはならないと心配する人もいるでしょう。しかし、そこまで問題にぶつかることなく、クラスやオブジェクトから、即効のかつ実践的なメリットを引き出すことができます。他の開発者は、ロータス スクリプトのオブジェクト指向は "正しく" できていないと聞いて(かつて私が実際に聞きました)、尻込みしているでしょう。ロータス スクリプトは、C++ や Java の全てのオブジェクト指向特性を持ってはいませんが、その利用法を知っていれば、とても便利な特性が揃っています。

この記事では、オブジェクト指向技術の便利さを証明する5つのクラスを展開します。個々のクラスは前述のクラスの内容を踏まえて、機能の増加を図ります。私たちは、コンポジション(composition)と継承(inheritance)によって、既存のクラスやデータ型を拡張する方法について学びます。ロータス スクリプトのビルト・イン・ノーツ・クラスからの継承は不可能であるため、コンポジションを使ってその制限を乗り越え、ビルト・イン・クラスを基礎とする新しいクラス作成方法を身につけます。私たちはまた、ベース・クラス、サブクラスについて学び、サブクラスがどのようにすれば継承してきた元のクラスの挙動を上書きすることができるかを学びます。クラスの利用が、どのようにして重複したコードを消去してノーツ・データベースを簡略化するのに役立つかを見ていきます。そして最後に、クラスによってイベントを上書きする方法を学び、コードを共有する新しい方法を身に付けます。 

ロータス スクリプトの、オブジェクト指向特性の全ての範囲を掌握するのは、この記事の主題を越えていますが、私たちは最も重要な特性を見ていき、詳しく学びたい方のためにリソースを推奨しております。他のオブジェクト指向を持つ言語については、あまり言及しない予定です。これらについての情報は、オブジェクト指向言語の理論の概観的理解に役立つでしょうが、オブジェクト指向のロータス スクリプトの作成にはすぐには役立たないからです。オブジェクト指向プログラミングについて、一般的な情報に興味のある方には、追加すべきリソースでいくつか本を挙げております。

この記事は、ロータス スクリプトを使うノーツ開発者に、その中でもオブジェクト指向プログラミングにまだ取り組まれたことのない方々向けのものです。この記事のコードは、ノーツ R5 において開発され試験されました。ノーツ 4.6 からもいくつか試験報告を得ております。Iris Sandbox(英語) にあるサンプル・データベースは、この記事で取り上げるコードを含んでおります。この記事を最大限に利用するために、スクリプト・ライブラリーとイベントについて理解しておくと良いでしょう。

基本的な定義

始める前に、定義についていくつか見てみましょう。オブジェクトは、何か名前や属性、メソッドを含むものです。属性はデータ値であり、メソッドはデータ値を操作できるコードを指します。クラスは dims、subs、関数のグループで、オブジェクトの属性を表現し、操作することができます。dims はオブジェクトの属性を保存し、subs と関数はオブジェクトのメソッド用のコードを含んでいます。クラスは特定のルーチン、すなわちプロパティーを含むことができます。プロパティーは、Private キーワードを使って "dim" されたデータ要素を取得し設定するのに使われます。

ロータス スクリプトでは、オブジェクトは New というキーワードを使って作成されます。あなたのコードには、同時に一つのクラスから沢山のオブジェクトを入れることができます。そしてそれらは互いにデータ値を共有しません。instance(インスタンス、事物)という単語は object の代わりに(または追加して)、この事実を強調するために使われます。

データ値、プロパティー、メソッド、そしてオブジェクトのメソッドは、ドットの左側にインスタンス、右側にデータ値、プロパティーあるいはメソッドを置いた「ドット表記」を使ってのみ照会されます。例えば、Book というクラスと Title というプロパティーがあったら、次のようなコードができます。

Dim ThingOne as New Book
Dim ThingTwo as New Book
ThingOne.Title = "The Cat In The Hat"
ThingTwo.Title = "Green Eggs And Ham"

ところで、オブジェクト指向プログラミングをよく観察した賢いプログラマーならば object の単語が全て「thingy (物質的な、実際的な)」に置き換わってもそう混乱することはないでしょう。では、thingy 指向のコードを見ていきしょう。

簡単なロータス スクリプト・クラスを作成する

まず最初に、簡単なロータス スクリプト・クラスについて考えましょう。これ自体では、あまり便利とは言えません。これは単なる文字列(string)と、それを識別するキー(key)値のコンテナ(container)なのです。焦らないでしばらく私の話を聞いてください。私たちが構築しようとしている他のクラスと連動することで、この簡易クラスはとても手軽な基礎的要素であることがご理解いただけると思います。

クラスのデータが Private と宣言され、アクセスはデータ要素への直接アクセスより、むしろプロパティー経由で提供されていることに注目してください。なぜこの新たなステップが必要なのでしょうか?これはコードが不可避的に変化するからです。これらのプロパティーが配置されることで、set プロパティーによってユーザーが与える値を有効にし、対応するデータ要素が無く get プロパティーから返された値を計算することができるのです。例えば、クラスは内部データ要素が英語のシステムに保存されても、メートル寸法(metric dimension)で返す新たなプロパティーを設置することができます。また、データ要素用の set プロパティーを放置しておくことで、質問内のデータ要素が private である限り、クラスのいかなるユーザーによるデータ要素の書き換えを防ぐことができます。もし、クラスのデータへの直接アクセスを許可するのであれば、得られない利点です。OO の世界では、プロパティー経由でデータへのアクセスを提供することをデータ隠ぺい(data hiding)と呼びます。クラスのデータ要素を公開と宣言することは可能ではありますが、一般的には、クラスのデータは private であるべきです。クラスのユーザーは、必要最小限のデータのみにアクセスするのに必要なプロパティー、関数、サブルーチンを提供されます。これにより、不適当な方法でクラスのデータを書き換えられるのを防ぐのです。

Public Class ListItem
' Note: this class issues an E_BLANK_KEY error if sub new or set key
' is passed a blank key 

   Private m_key As String
   Private m_value As String

   Sub new (key As String, value As String)
     If key = "" Then 'don't allow a blank key
       Error E_BLANK_KEY, E_BLANK_KEY_MSG
     End If

     m_key = key
     m_value = value
   End Sub

   Property Get key As String
     key= m_key
   End Property

   Property Set key As String
     If key = "" Then 'don't allow a blank key
       Error E_BLANK_KEY, E_BLANK_KEY_MSG
     End If
     m_key = key
   End Property

   Property Get value As String
     value= m_value
   End Property
   Property Set value As String
     m_value = value
   End Property

   Sub PrintItem
       Print "ListItem: key = " & Me.m_key & " value = " & Me.m_value
   End Sub

End Class

Sub New メソッドと Set Key プロパティーを見てみると、これらは空白キーに遭遇するとエラーを発生します。何故でしょう?単に、これが問題の発生を知らせるのに一番良い方法だからです。プロパティーもメソッドも、エラー状態を知らせるための有効な返り値を持っていないのです。

コンポジションを使ってクラスを作成する

ListItem クラスでは、何ができるでしょうか?他のクラスなしでは多くのことはできません。このクラスを便利なものにするために、何か別のクラスを作ってみましょう。ロータス スクリプトのリスト・データ型は、非常に便利なツールです。これを最大限に利用すると、しかしながら、自分で足跡をつけて(またはリスト内を巡回)いかなければ、リスト内の要素をカウントする方法が無いことにお気付きになるでしょう。以下のクラスがリストを覆う "ラップ(wrapper)" となり、問題解決になるでしょう。(BetterList クラスで、このコードの中断のないバージョンが見られます)

Public Class BetterList

   Private m_list List As Variant
   Private m_count As Integer

   Property Get Count As Integer
     Count = m_count
   End Property

   Public Function DeleteList
     Erase m_list
   End Function

   Public Sub new
     m_count = 0
   End Sub

   Sub Delete
     Call Me.DeleteList
   End Sub

クラスの変数が全て m_ という文字列で始まることに注目してください。これは C++ 世界での慣習から借りてきました。これにより一目でどの変数が、現在の関数でなくこのクラス自身に属するかが分かります。この方法は、特に大きなクラスにおいて、時間を短縮します。m_list もまた、private であることに注目してください。このクラスを使う人には、リストに手を加える方法は無いのです。これが、カウントは正常であることを保証する唯一の方法なのです。

次は BetterList クラスの関数(またはメソッド)です。普通の関数と変わりありませんが、クラスの中で定義されています。

   Public Function DeleteItem(key As String) As Integer
     Dim rval As Integer

     'if the key is in the list, erase the object
     If ( Iselement(m_list(key)) ) Then
       Erase m_list(key)
       m_count = m_count-1
       rval = True
     Else
       'if there's no such key, warn of an error
       rval = False
       Print "Item " & key & " not found. It could not be deleted."
     End If

   DeleteItem = rval
End Function

Public Function AddItem(key As String, item As ListItem) As Integer

   'just add the item if the key doesn't exist
   If ( Not Iselement(m_List(key)) ) Then
     Set m_list(key) = item
     m_count = m_count+1
   Else
     'if the key does exist, erase the current object in the list
     'and add the new one
     Erase m_List(key)
     Set m_list(key) = item 
   End If

End Function

コードの次のセクションでは、GetItem が variant 型の値を返します。何故でしょう?関数が ListItem を返すことはできても、今度は私たちが対処したいオブジェクトを一つ一つ返すための、BetterList のようなクラスを設けなければならないのです。これにより、一つのクラスが多数のオブジェクト・タイプを受け持つことができます。variant 型の値をこのように使うことは、注意しないと問題の原因にもなります。もし GetItem が誤ったタイプのアイテム(item)を返している場合、あなたが期待しているタイプとして扱っていると、ランタイム・エラーに遭遇するでしょう。

次に考えるクラス EnhancedUIDoc で、この問題に光明を見出すことができます。私たちは、すぐさま正しいオブジェクト・タイプに variant 型を割り当て、さらなるアクセスはオブジェクトを経由して行なわれるようにします。この EnhancedUIDoc における割り当ては、ランタイムよりむしろコンパイル時に、プロパティー名や関数におけるエラーを知らせるという効果があります。私は、いつでもエラーを見つけられるコンパイラーを選びます。これは試験によって問題を見つけるよりずっと簡単で、時間も節約できるからです。)

次がこのクラスの実働部関数です。リストに保存しているオブジェクトにアクセスするために使われます。

また、すぐ下にエラー対処用コードを置いているのがお分かりになるでしょう。これはエラーの種類と、エラーが発生したクラス、メソッド(またはプロパティー)を表示します。そして続行過程への進行を許可します。基礎的ですが、エラーの発端を特定するのに極めて便利です。これが無くては、コードの中の何処で問題が起こったのかを知る前に、沢山の退屈なデバッグ作業をしなくてはなりません。もしあなたのアプリケーションが複雑で、クラスの使用が広範囲に渡るものなら、この実践が大きく時間を節約することにお気付きになるでしょう。また、エラーを記録するためのデータベースを作成したくなるかも知れません。

   Public Function GetItem(key As String) As Variant
     Dim itm As ListItem
     On Error ErrListItemDoesNotExist Goto NoSuchItem
     Set GetItem = m_list(key)

OK:
     Exit Function

NoSuchItem:
     'return a value of Nothing if the key was not found
     Print "List item " & key & " not found."
     Set itm= Nothing
     Set GetItem = itm
     Resume OK

   End Function

   'see if there's an object in the list for a given key
   Public Function IsInList(key As String) As Integer
     Dim rval As Integer

     If ( Iselement(m_List(key)) ) Then
       rval = True
     Else
       rval = False
     End If

     IsInList = rval
   End Function

End Class

クラスの中の関数とサブルーチン(例えば、ここで使われる GetItem や AddItem です)は頻繁に OO 世界におけるメソッドと呼ばれます。クラス内の Sub New サブルーチンは、特別な名前を持っています。それは、コンストラクター(constructor、建設者)です。そのクラスのオブジェクトが作成されると、自動的に呼び出されます。このサブルーチンは、クラスが必要とした全ての初期化とセットアップ作業に広く使われています。

もう一つの特別なサブルーチン、Sub Delete も利用することができます。これは逆にデストラクター(destructor、破壊者)として知られています。これは、クラスのオブジェクトが削除された時に、自動的に呼び出されます。この2つの特別サブルーチンは、どちらもあえて要求されることはありません。コンストラクターは便利すぎるため、私は放置しておくことなどはほとんどありません。デストラクターについては、メモリー管理についての問題に対処することが無ければ、あまり気に留めていません。メモリー管理については、この記事のオブジェクトとメモリーの管理のセクションをご覧ください。

コンポジションの理論

ところで、こういった方法でクラスを構築することの技術的定義を、コンポジション(composition、合成体)と呼んでいます。または、アグリゲイション(aggregation、集合体)としても知られています。このケースにおいて、前もって定義されたデータ型を含んだ新しいクラスを構築してきました。クラスはまた、オブジェクトそのものであるデータ要素を含むこともできます。それがどのように機能するか、これから見ていきましょう。

OO の世界では、コンポションあるいはアグリゲイションは "has a(持つ)" 関係と呼ばれています。車はエンジンやタイヤを "持つ"、果物は種子や皮を "持つ"、と言う具合にです。多くの本はオブジェクト指向言語を、これらの例に基づいたダイアグラムやコードを使って説明しています。そういったダイアグラムは、OO のコンセプトを図解していて(次の図は私のバージョンですが)役立ちます。しかし、これらダイアグラムと従来から対応するコードについては自信がありません。確かに働きますが、何一つ便利なものでは無いのです。私はこの記事に含まれるコードは OO 技術を使う時に有効だと思います。オブジェクト指向的に便利なことをする方法を示しています。

building classes by composition
building classes by composition

ビルト・イン・クラスを拡張してクラスを作成する

BetterList クラスは便利そのものです。標準 Notes リストができないことをやってくれるのです。しかし、私たちはそこで終わりにはしません。今作成した2つのクラスを使って、データベースの全てのフォームに共通の特性を加えるのに使う、新しいクラスを構築します。私達は、通常のままでは好まれない特性さえも変更できるようになります。ロータス スクリプトのビルト・イン Notes クラスを継承する、新しいクラスを作成することはできないため、私達は再度コンポジション技術を使って、 NotesUIDocument フロントエンド・クラスの能力を拡張します。このクラスを EnhanceUIDoc と呼ぶことにしましょう。

このクラスから得られる主要な利点の一つは、フォーム内のあらゆるフィールドに発生した変更を感知する能力です。ノーツ・アプリケーションでは、追従するフィールド一つ一つに目に見えない "影の" フィールドを設けることによって頻繁に感知作業をやっています。EnhanceUIDoc を使うことで、フィールドを追加する必要性を無くすことができ、したがってフォームを簡単にし、開発のための時間を節約することができます。もし全てのフィールドの変更跡を追いたくなければ、EnhancedUIDoc を継承するクラスを作成して自分のフィールドのリストをチェックするようにすることができます。プロファイル文書は、そういったリストをロードする元の固定場所の一つとなるでしょう。(継承がどのように働くかは間もなく見ていきます。実際に EnhancedUIDoc をベース・クラスとして後の継承の例で使います。)

簡略化のために、このクラスでは全てのフィールド値が文字列(strings)で、かつ一フィールドにつき一つの値が与えられるものとします。これにより、このクラスを変更して、様々なフィールド・タイプや多数の値に対処し易くなります。フィールド・タイプは、 NotesItem により決定されます。プロパティー・タイプ。NotesItem 。Values プロパティーは、フィールドが単一の値、複数の値のどちらを持つか決定するために、カウントされる配列を返します。

次に示すのが EnhancedUIDoc クラスで、BetterList クラスを含んでいます。
EnhancedUIDoc クラスで、このコードの中断のないバージョンが見られます)

Class EnhancedUIDoc
   Private m_uidoc As NotesUIDocument
   Private m_uiw As NotesUIWorkspace
   Private m_origvalues As BetterList
   Private m_doctype As String

   Sub ProcessPostopen( Source As NotesUIDocument )
     Dim doc As NotesDocument
     Dim ltm As ListItem

     Print ("EnhancedUIDoc - ProcessPostopen")

     Set doc = m_uidoc.document
     Forall i In doc.Items
     Set ltm = New ListItem( i.Name, i.Values(0) )
     Call m_origvalues.AddItem(i.Name, ltm)
     End Forall

End Sub

   Sub ProcessQuerysave(Source As Notesuidocument, Continue As Variant)
     Dim doc As NotesDocument
     Dim ltm As ListItem
     Dim rval As Integer
     Dim v As Variant

     Print ("EnhancedUIDoc - ProcessQuerysave")
     rval = continue
 
     Set doc = m_uidoc.document
     Forall i In doc.Items
       Set v = m_origvalues.GetItem(i.Name)

ここがオブジェクトに variant型の値を返すところです。最初が null でないことを確かめなくてはなりません。そうしないと、Itm が真のオブジェクトではなくなってしまいます。

     If (Not Isnull(v) ) Then 'make sure there's an item to compare it to
       Set ltm = v
       If i.Values(0) <> ltm.value Then
         Print "Item " & i.Name & " new value = " & i.Values(0)
         rval = True
       Else
         Print "Item " & i.Name & " not changed."
       End If
     Else
       Print "Item " & i.Name & " not found."
     End If

   End Forall

   Continue = rval
End Sub

Sub new (uid As NotesUIDocument)
   Print ("EnhancedUIDoc - sub new")

   Set m_uiw = g_wks
   Set m_origvalues = New BetterList
   Set m_uidoc = uid

次の行では、On Event 命令文を見てください。ここでは何が起こっているのでしょう?一般的なイベントをあつかうサブルーチンを、私たちのクラスで定義したサブルーチンで置き換えているのです。これが、クラスを使う際に、自分達のコードを統一することのできるもう一つの方法です。ProcessQuerysave と ProcessPostopen でのコードは、フォームの Querysave と Postopen のサブルーチンに入れることもできました。しかし、それはクラス内の残りの部分へのコードから、これらのイベントへのコードを分離してしまうことになるのです。

     On Event Querysave From m_uidoc Call ProcessQuerysave
     On Event Postopen From m_uidoc Call ProcessPostopen

   End Sub

   Sub Postopen(Source As Notesuidocument)
   End Sub

   Sub Querysave(Source As Notesuidocument, Continue As Variant)
   End Sub 

End Class

このクラスはコンポジションを使って作成されていますが、これはまた私達の継承の例の基礎にもなります。何故なら、次の2つのクラスはこのクラスを継承するものだからです。

継承の理論

継承の基本について取りあつかう前に、まず簡単にその理論について見てみましょう。OO の本では、継承はしばしば "is a" 関係と呼ばれています。車は乗り物の種類 "である"、ポルシェは車の種類 "である"、などです。バナナ、リンゴ、さくらんぼは、全て果物の種類 "である" のです。次の図は、もう一つの伝統的な OO ダイアグラムです。

Building classes by inheritance
Building classes by inheritance

サブクラスは、もう一つのクラスを継承するクラスです。サブクラスはまた、派生クラス、あるいは子クラスとしても知られています。サブクラスは、他のサブクラスを継承することもあります。上のダイアグラムでは、ジープと牽引トラックは両方とも他のサブクラスを継承するサブクラスです。ベース(基本)クラスは、単純に他のクラスを継承しないクラスのことです。ListItem、BetterList、EnhancedUIDoc は、全てベース・クラスです。BetterList と EnhancedUIDoc がコンポジションを活用し、したがって他のクラスを含んでも違いは無いのです。どちらも他のクラスを継承しないので、それがベース・クラスとなるのです。上のダイアグラムでは、乗り物が唯一のベース・クラスです。

もしあなたが私と同じなら、頭を頷き、「いやぁ、この継承ってのは大したものだが、これで何ができるんだい?」と思っているかも知れませんね。クラスに組み込まれたあなたのコードがあれば、あなたは既存のクラスに基づいて簡単に新しいクラスを作成できるのです。新しく書くコードは、親のコードが欠いた能力を追加するものです。将来の開発をより早く簡単に行なえるでしょう。加えて、コードが重複し得る状況を消し去ることになります。さあ、この理論を実践に移す方法を見て行きましょう。

継承を使ってクラスを作成する

私達は、単純に全てのフォームから必要な能力だけを EnhancedUIDoc に入れ、そのままにしておくことができました。私達は、既に重要な利点を引き出しています。しかし、私達はさらに改善することができます。継承を利用することで、私達は既存のクラスの動作を高め、変更し、あるいは上書きすることができます。

これを行なう一つの方法として、EnhancedUIDoc を抽象型ベース・クラスとして用いる方法があります。抽象型クラスは、作成されるために存在しません(または、より正式な OO 用語を用いれば、インスタンスを作成するために存在しません)。これらは他のクラスが継承するためだけのベース・クラスとして存在します。ロータス スクリプトにおいて、クラスを抽象型化させることは簡単ではありません(つまり、誰かにこのクラスを直接使われることを防ぐことはできません)。しかし、実際抽象型化を強要する必要はないのです。私達はフォームを作成する際、EnhancedUIDoc を既成オブジェクトとして利用することができ、それはちょうど上手く働きます。しかし、それを抽象型クラスとしてあつかうこともでき、そしてそれには長所があると思われるのです。共通の動作を、継承したい個々のフォームに一つの抽象型クラスと特定のサブクラスを用いることは、より融通の利く機構だと考えられます。このようにすることで、あるフォームのためにカスタマイズされたあらゆるコードは、そのフォームに提携するサブクラスの中に孤立します。同じように、全てのフォームを対象とするコードは、ベース・クラスの中に孤立化します。

さぁ、NotesUIDocument を拡張するクラスができました。いつ、どこでこのクラスを作成すれば、正しい UI 文書を指し示すでしょうか?思い出してください、この作業は様々なスタートアップ・イベントが立ち上がる前に終わらなくてはなりません。したがって、これらのイベントが起こったら、それを上書きするようにしなくてはなりません。私達は、この作業をフォーム内の Queryopen イベントで行ないます。NotesUIWorkspace よりも前に行なうことはできません。というのは、CurrentDocument プロパティーがこのイベントの前に使用可能な値を返さないからです。

Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)

   Set g_CurrUIDoc = New EnhancedUIDoc(Source)

End Sub

グローバル変数 g_CurrrUIDoc が使用され、現在の目に見える文書ウィンドウに属するオブジェクトを追跡します。

ここに EnhancedUIDoc を継承する2つのクラスがあります。1行目で、SubClassUIDoc クラスが EnhancedUIDoc を継承することを As キーワードを使って宣言しています。As の後にリストされたクラスは、新しいクラスが継承する元のクラスです。この2つのクラスは両方とも EnhancedUIDoc を継承するサブクラスです。その一方、SubclassUIDoc は何も変更をさせないものの、メソッドが呼ばれる順番の図示に役立つプリントの命令文を搭載しています。もう一方、NewEventUIDoc は EnhancedUIDoc に見られない特性を与えるコードを含んでいます。NewEventUIDoc を使って作成された文書はルーラー、水平スクロール・バーが表示されます。これらの新特性は、ProcessSpecialPostopen メソッドにおいて付加されます。また NewEventUIDoc は、このベース・クラスである NotesUIDocument に入れたフィールド追跡能力を無効化します。これは "on event … remove" ステートメントにより実行されます。

Class SubclassUIdoc As EnhancedUIDoc

   Sub new (uid As NotesUIDocument)
     Print ("SubclassUIDoc - sub new")
   End Sub

End Class

Class NewEventUIDoc As EnhancedUIDoc

   Sub new (uid As NotesUIDocument)
     Print ("NewEventUIDoc - sub new")
     On Event QuerySave From m_uidoc Remove
     On Event Querysave From m_uidoc Call ProcessSpecialQuerysave
     On Event Postopen From m_uidoc Call ProcessSpecialPostopen
   End Sub

   Sub ProcessSpecialQuerySave (Source As Notesuidocument, Continue As     Variant)

     Print ("NewEventUIDoc - ProcessSpecialQuerySave")
   End Sub

   Sub ProcessSpecialPostOpen( Source As NotesUIDocument )
     m_uidoc.HorzScrollBar = True
     m_uidoc.Ruler = True
   End Sub

End Class

"On Event QuerySave From m_uidoc Remove" というステートメントに注目してください。この行が無くては、NewEventUIDoc クラスのオブジェクトの保存の前に、 ProcessSpecialQuerySave と ProcessQuerysave の呼び出しを行なうことになります。ロータス スクリプト文書からは、オブジェクトにおけるイベント・サブルーチンの呼び出しの順番は定義されていないことが分かります。どのサブルーチンが最初に呼び出されるか、正確に知る方法はありません。したがって、特定のイベントへのサブルーチンが、他よりも先に呼び出されるのを前提とするコードを書かないよう気を付けることです。この例では、本来のサブルーチンを無効化することで問題を回避しています。

私達は2つのサブクラスを作成してきましたが、ここで新たな問題について考えなくてはなりません。サブクラスのオブジェクトが作成されたら、サブクラスとベース・クラスのコンストラクターはどうなるのでしょうか?私達がサブクラスのオブジェクトを作成すると、サブクラスの Sub New を呼び出しますが、これはまた継承する元のクラスの Sub New をも呼び出してしまうのです。言い方を変えると、ベース・クラスの Sub New が最初に呼び出され、サブクラスの Sub New がそれに続くのです。逆のことがデストラクターを呼び出した場合に起こります。呼び出しは最初サブクラスのデストラクターに行なわれ、次にベース・クラスのデストラクターに行なわれます。この工程の図示に役立つ命令文がコードの中に入っています。これを見るために、Iris Sandbox(英語) からサンプル・データベースをダウンロードし、SubclassUIDoc 文書を作ってコンストラクターが呼ばれる順番を観察することができます。

コンストラクターとデストラクターは、これらが継承する元のクラスのメソッドを自動的に呼び出す唯一のクラス・メソッドです。コンストラクターは、引数を取ることがあります。もしそうなれば、2つのサブクラスの状況が発生します。サブクラスの引数(argument)が親クラスの引数に一致すれば、全てが上手く行きます。一致しない場合、サブクラス・コンストラクターは、どの引数が親コンストラクターに伝わればよいかを宣言しなくてはなりません。これをどのように行なうかについての詳細は R5 デザイナー・ヘルプのプロパティーとメソッドの上書きトピックをご覧ください。デストラクターは引数を取りません。

プロパティーとメソッドを上書きする

サブクラスは、親クラスのプロパティーとメソッドを上書きすることができます。これはサブクラスにあるプロパティー、またはメソッドを、親クラスにおけるそれと同じ名前で定義することで可能です。パラメーター・リストは、この2つのアイテムで全く同一でなくてはなりません。なぜ、このようにするのが良いのでしょう?一つの理由としては、ベース・クラスとサブクラスの両方が RestoreDefaultValues メソッドを持っていることが考えられます。個々のクラスは、この能力を必要とします。しかしこのそれぞれは、異なった初期値をリストアします。もしサブクラスが、ベース・クラスにおいて既に可能なことを必要としたら?コードを複写することは、良い方法ではないことは分かっています。サブクラスはドット(..)表記法を使って、継承した元のクラスのどのような上書きされたプロパティーや、あるいはメソッドを呼び出すことができます。ドット表記法は、クラス内でのみ有効です。これをどのように行なうかについての詳細は、R5 デザイナー・ヘルプのドット表記法エントリーをご覧ください。

オブジェクトとメモリーの管理

ロータス スクリプトは、あなたのために、ほとんどのメモリー管理問題を手がけていますが、オブジェクトをあつかう時、特にリストにオブジェクトを保存する時には、このロータス スクリプトにより注意する必要があります。これはなぜでしょう?あなたの作成するオブジェクトは、全てある量のメモリーを必要とします。あなたが既に作業を終えたオブジェクトを消すことなく、大量にオブジェクトを作成し続ければ、あなたのコンピューターは動けなくなり、クラッシュさえすることがあります。一つの関数の範囲内で作成されたオブジェクトならば、関数が終わり次第自動的に整理されます。しかし、リストの中にあるオブジェクトは、リスト自身が解放されなければ解放されません。これは、もしリスト・オブジェクトに関わるグローバル変数があれば、当該データベースが閉じられるまで解放されません。

一度に開くことのできる文書の数に制限があり、かつ文書内のフィールド数が通常数百以下であれば、EnhancedUIDoc でこの問題に直面する人はまずいないでしょう。もう一方で、データベース内の個々の文書に対して複雑なオブジェクトを作り、その一つ一つをリストに保存しようとするアプリケーションは、データベース内の文書数がこれらを描写するオブジェクトの保存可能数を越えた時に、大抵メモリー不足に苦しみます。

この問題を避けるには、どうすれば良いのでしょう?使い終わったオブジェクトは、確実に消去することです。また、一度に過剰なオブジェクトを利用可能にする必要のないコードを作成しましょう。Erase ステートメントは、リストから全てのオブジェクトを取り除くことができます。あるいは、一つのオブジェクトだけ取り除くことも可能です。

制限

オブジェクトをご自身で利用し始めるなら、いくつか知っておくべきことがあります。オブジェクトは、スクリプトの宣言セクション内で定義されなくてはなりません。そしてセクション内に置けるコードの量は、64k バイトという制限があります。もしあなたがクラスをたくさん作成したら、一つのスクリプト・ライブラリーには収めきれないでしょう。もしあなたのスクリプト・ライブラリーで作業中、または保存中に "バッファーが一杯なので現在の作業を中断します" というメッセージが現れたら、あなたは 64K の制限に達したことになります。大き過ぎるスクリプト・ライブラリーをいくつかに分割し、関連するクラスでつなぎ合わせることを考えましょう。

時には、イベントで作業するのがやり難くなる場合があります。全てのイベントにおいて、全てのビルト・イン Notes オブジェクトが利用可能ではないのです。具体的には、フォームの Queryopen イベントの前に NotesUIDocument オブジェクトにアクセスすることはできません。また、NotesUIDocument より前に、文書用の Notes バックエンド文書を作成できません。文書自体も、Postopen イベント前には利用できないのです。

また、ロータス スクリプトは、R4 と R5 とではそのコンパイル方法が異なることを心に留めて置いてください。R4 で、クラス A とクラス B を、クラス A がクラス B のオブジェクトを含んでいるのにクラス B よりも先に定義される、このように定義づけるスクリプト・ライブラリーを保存しようとするとします。この場合、"クラスまたはタイプ名が見つかりません" というエラーが表示されます。このコードは、R5 では正常に保存されるのです。ノーツのこの両バージョン上で開発する場合は、この事情をしっかり認識してください。

最後に、注意事を一つ。オブジェクト指向という言葉を誇示するためだけに、クラスにあなたのコードを全て詰め込まないことです。オブジェクト指向だからといって、それだけでは良いコードではないことを忘れてはいけません。悪い OO コードを書いてしまう可能性は十分にあるのです。とりわけ、3、ないし4段階に及ぶ継承を使うコードは、理解するのが難しいこともあります。つじつまの合うように、時と場所を選んでコードを利用しましょう。いかなるコードにおいても、良いコメントと文書化は、あなたの OO コードの利用と保存をより簡単にします。

まとめ

私達は、5つのロータス スクリプト・クラスを、単純なものからより複雑なものまで吟味してきました。これらは、2つの主要な技術を使っています。それは、コンポジションと継承です。私達はコンポジションがどのようにビルト・イン Notes オブジェクトを、継承にはできない方法で拡張するのに用いられるかを見てきました。また私達は、継承を使って、特定の増大や例外を許可しながら、フォーム間の基本的な動作を共有してきました。私達は、ロータス スクリプトのクラスにおいて、どのようにしてイベントを上書きするかを見て、エラー処理、メモリー管理、そして制限を振り返りました。

全体を振り返り、私達はロータス スクリプトのオブジェクト指向特性がどのようにしてロータス スクリプトに新しい能力を与え、そしてクラスがどのようにして重複コードの回避に役立つかを説明してきました。ここに挙げられた用例があなたにひらめきを与え、あなた独自の便利なクラスを作成されることを願う次第です。

追加すべきリソース

コード
Iris Sandbox(英語) から、この記事で取り上げたコードを含むデータベースをダウンロードできます。

R5 デザイナー・ヘルプ
R5 デザイナー・ヘルプは、オブジェクトに関するいくつか便利なエントリーを持っています。ただし、どこを見るべきか知っていなくてはなりません。出発点として、"ロータス スクリプト言語" トピックの "ユーザー定義のデータ型とクラス" サブセクションを試してみましょう。

また、"プロパティーとメソッドの上書き" トピック内の、次のトピックから関心深いものを見つけるかも知れません。

  • 派生クラス用に Sub New を拡張する
  • Sub New と Sub Delete を呼び出す
  • ベース・クラスのプロパティーとメソッドにアクセスする
  • オブジェクト参照を引数と戻り値として使う
  • 派生クラス・オブジェクトに Set ステートメントを使う

書籍
オブジェクト指向プログラミングをあつかう本で、おそらく最も名前の知られる2冊は、Graby Booch 著の Object-Oriented Analysis and Design With Applications(Addison-Wesley 社 1994 年)と、Eric Gamma、Richard Helm、Ralph Johnson、John Vlissides 共著、Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley 社 1995 年)でしょう。Design Patterns は、よく知られたクラスの組み合わせに着目しています。

Web サイト
パターンは、頻繁にクラスの組み合わせが使われています。より詳しく学ぶ際の助けになるように、2つの Web サイトを紹介しておきます。Patterns FAQ(英語) Web サイトはよく聞かれる質問への回答を含んでいます。Patterns Home Page(英語) Web サイトはチュートリアルを掲載しています。

謝辞
この記事の構成と向上に、数々の提案をしてくださったリチャード・シュワルツ氏に感謝しております。またニック・シュローニィ氏に、彼に私の仕事を混ぜていただいた事に感謝申し上げます。彼が 1999 年、ロータス ビジネス・パートナー・テック・フォーラムに著した UIDocument クラスは EnhancedUIDoc を着想させました。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Lotus
ArticleID=341459
ArticleTitle=Iris Today Archives: ロータス スクリプトのオブジェクト指向特性を使う
publish-date=10012001