こんにちは。このプレゼンテーションをご覧いただきありがとうございます。JMPのクラスと、それらをJSLで操作して動的かつ拡張可能なインターフェース動作を実装する方法についてお話します。
ベン・バレットです。IDEXX Laboratoriesでエンジニアとして約10年間働いています。LabVIEW、MATLABなど様々なアプリケーションを使い、C#も少し触ってみました。これらの言語を使う中で、私は常にオブジェクト指向プログラミングスタイルに惹かれてきました。
なぜか直感的に理解できるんです。でも、JSLでオブジェクトを扱い始めたら、その直感があまり役に立たないことに気づきました。いくつか微妙なニュアンスを克服する必要がありましたが、使い方がわかってくると、とても便利だと分かりました。
まずは簡単に概要を説明します。JMPのクラスについて詳しく説明するつもりはありません。JMPを使い始めた時の私の発見のプロセスを振り返っていきたいだけです。難しいと感じた点、直面した問題点とそれをどのように克服したか、そして最後にJMPを使った素晴らしい例をいくつか紹介します。
大まかなアジェンダは以下の通りです。私がどれだけうまく守れるか見てみましょう。スコープについて少しお話ししたいと思います。これは、私がいくつかのクラスを実装した際に、常に問題となっていたため、私が見つけたベストプラクティスをいくつか紹介したいと思います。また、JSLのクラスが、C#などの他の言語で期待されるものと少し異なる点についてもお話ししたいと思います。その後、実際に機能するデモコードをいくつか紹介します。
JSLのクラスやオブジェクト指向設計の原則全般について、深く掘り下げた解説は期待しないでください。ただし、これからクラスについて深く掘り下げていこうとする場合、本書が少しでも頭を悩ませるのを避けるのに役立つことを願っています。
議題はこれです。ここまで全て説明しました。JSLで利用できる様々なスコープの概要を簡単に説明します。これらがどのように相互作用し、どのように使えるのかを完全に理解しているわけではありませんが、基本的な考え方はいくつかあります。
一般的なスコープは4つあります。ローカルスコープ、グローバルスコープ、そして独自のカスタム名前空間です。このドキュメントでは、それぞれのスコープの操作方法について概説しました。より詳しい情報を知りたい場合は、オンラインドキュメントを参照することを強くお勧めします。しかし、一般的に、クラスを扱う場合、私が見つけた最良のアプローチは名前空間を使うことです。
クラス内で参照する必要がある場合は、独自のカスタム名前空間を作成してください。これにより、多くのスコープや競合の問題を回避できました。また、クラスに関してはHere名前空間はあまり信頼できないことがわかったので、名前のデフォルトをHereに設定しないようにしています。さらに、local Hereコマンドを使用しない限り、ローカル変数の使用も役立ちます。ただし、どの変数をローカルにするかを明示的に定義する必要があるため、メンテナンスが少し面倒になります。デモを見ていくうちに、この点についてはより明確になるでしょう。
ベストプラクティスについて説明しました。デフォルトの名前空間は避け、カスタム名前空間を使用し、クラスも同様に使用してください。クラスは、ある意味ではコピー可能な名前空間と考えることができ、同時にメモリ内に重複したクラスを持つことができます。JSLクラスは、皆さんが馴染みのあるクラスとどう違うのでしょうか?クラスには他にも期待される機能があると思いますが、ここで挙げた5つは、オブジェクトに期待される機能をかなり標準的に表していると思います。
カプセル化、継承、ポリモーフィズム、レプリケーション、そして抽象化。継承、ポリモーフィズム、抽象化はすべて互いに関連しており、JSLのクラスはカプセル化、継承、レプリケーションをかなり適切にサポートしていますが、ポリモーフィックな動作と抽象化の実装には微妙なニュアンスがあります。これら5つの点について簡単に説明します。
カプセル化は十分にサポートされています。クラスを定義する際には、そのクラスに関連付けられたプロパティとメソッドを定義します。ここでの唯一のニュアンスは、これらのどれも当てはまらないということです。クラスの真にプライベートなメンバーを定義することはできません。すべてがパブリックです。これは名前空間のようなものです。クラスのメンバーとプロパティにはグローバルアクセスが可能です。
継承もサポートされています。別のクラスから派生したクラスを作成すると、その子クラスは親クラスのメンバを継承します。ポリモーフィズムにはいくつかの微妙な違いがありますが、これについては後ほど詳しく説明します。ポリモーフィズムを真に実装するには、親クラスが自身のメソッド内から子クラスに適切にディスパッチできるようにするための特別な回避策が必要です。
レプリケーション。これこそが、クラスを使うべき理由の核心です。オブジェクトの複数のコピーをインスタンス化することができ、それぞれが独自のカプセル化されたパラメータとメソッドのセットを持つことになります。私は今のところ、この点で特に問題に遭遇したことはありません。
次に抽象化についてですが、JSLクラスには明示的な抽象化の概念はありませんが、エラーを返したり、基本的な動作を実行したりするデフォルトメソッドを持つ親クラスを定義することができます。C#や他の言語に慣れている方にとっては、これは抽象メソッドというより仮想メソッドに近いかもしれません。これが、JSLのクラスが、他の言語から来た方にとって馴染みのあるクラスとどのように異なるかについての、大まかな概要です。JSLのクラスについて簡単に説明しましたが、それでは本題に入りましょう。
JMPインスタンスを開きました。いくつか簡単な例を挙げてみました。まずは簡単な例から始め、次に少し複雑な例、そしてさらにずっと複雑な例へと進めていきます。まずは、非常に基本的なプログレスバーについてお話ししたいと思います。
これらのデモはすべて、GUI開発の拡張性とモジュール性を高め、オブジェクトの機能を最大限に活用できるようにすることを目的としています。このプログレスバーは、実際に動作を確認できるよう、簡単に実行してみます。このプログレスバーの基本的な実装は、オンラインのどこかの投稿で見つけたもので、誰かが質問していました。プログレスバーの作り方を詳しく説明しますが、今回のデモではそれほど重要ではありません。
このコードは基本的に、プログレスバーの外観を定義するためのいくつかの設定を行っています。ウィンドウを作成しています。ここでオブジェクトを作成しているのがわかります。オブジェクトのコードについては後ほど説明します。次に、ウィンドウ内にオブジェクトを配置し、実行中のタスクを表すためにランダムな要素を作成しています。その後、何らかの処理を実行しているかのように反復処理を行い、クラス定義自体に含まれるメソッドを使ってプログレスバーを更新しています。
ここではターンはありませんが、このオブジェクトはこの視覚化を作成するすべての動作をカプセル化しています。ウィンドウを表示させてください。コードができました。プログレスバーのクラス定義に移動し、これらをすぐに最小化してみましょう。クラスはdefine class呼び出しを使用して定義し、名前を付けてインスタンス化します。クラス内での変数の定義は…他の変数を定義するのと同じ方法で定義できます。
この値はクラスのインスタンスに割り当てられているので、クラス参照と名前を使ってアクセスします。クラス内のメソッドは、関数を定義するのと同じ方法で定義されます。本質的には別の名前を持つ関数であり、クラスオブジェクトに割り当てられます。先ほど述べたように、クラスは名前空間のようなもので、クラスのインスタンスを作成した後は、これらすべてを操作できます。作成したオブジェクトに割り当てるプロパティを追加することもできます。JMPクラスでは、あらゆる動的な処理が可能です。
あまり詳しく説明するつもりはありません。このプログレスバーがどのように作成されるかに焦点を当てたいと思います。initialize関数が実行されます。これをサイドビューに表示してみましょう。この例では、オブジェクトを作成するとinitialize関数が実行されます。新しいオブジェクトの呼び出しは自動的に_initを実行します。
初期化時にデフォルトの設定がここに設定されますが、必要に応じて任意の設定を指定することもできます。これは基本的な設定をすべて行い、最後にビルドコマンドを呼び出します。ビルドメソッドはここで定義されています。ビルドメソッドでは、プログレスバーの外観を構成するすべてのオブジェクトを作成します。コンテナといくつかのスペーサーボックスがあり、このスペーサーボックスを拡大縮小することで進捗状況を表現することができます。繰り返しになりますが、これはJMPコミュニティの投稿から引用したものです。ソースコードは手元にありませんが、これに関する投稿がありました。
これはプログレスバーの外観に関する詳細をすべて設定するだけです。ここには高さや幅を変更するヘルパーメソッドがいくつかあり、これらはすべて呼び出すことができます。それから、どのように操作して進捗状況の量を変更するかという問題もあります。これらは単なるメソッドです。それぞれ異なる機能を持っています。例えば、set は… パーセンテージを明示的に値に設定します。Step は一定量ずつ進めます。その他、必要な設定を定義できます。
ステップ数を正確に指定して、反復処理のたびにステップを進めることができます。ここでメソッドを操作すれば、特定のタスクに合わせて自由に変更できます。ここで例に戻って、コンテキストがわかったところで、これは非常に簡単です。オブジェクトを作成し、その後…ここで新しいウィンドウを設定する際に、そのオブジェクトを活用するために、実際の表示要素を取得できるようにする必要があります。
プログレスバーでは、高レベルのコンテナはここにあるHリストボックスです。オブジェクトを取得した場合、その値またはコンテナを返すだけです。新しいウィンドウ定義で、プログレスバーを特定の位置に配置したい場合は、オブジェクトを取得するか、作成したオブジェクトを取得して取得します…これは「パネルを取得する」のように明確に表現できます。その方が分かりやすいのですが、現状はこうです。
とにかく、これが挿入方法です。そしてここでは、オブジェクト参照をまだ参照していて、プライマリバーについてはステップ実行し、セカンダリバーについては明示的な値を設定しています。ここでテキストを更新しています。もう一度確認して、何が行われているかを確認しましょう。繰り返しますが、ランダムな値を入力して更新しているだけです。これを他のタスクに挿入する方法は想像できるでしょう。
もう一つ、私が便利だと感じた点として、プログレスバーを閉じることができる点を指摘しておきます。そうすると、次に反復処理を実行しようとした際に、ウィンドウへの参照が失われるため失敗します。プログレスバーを閉じた状態で、この update を実行してウィンドウを更新しようとしても、更新できません。そのため、エラーが返されます。
これはすべてtry文でラップされているので、失敗するとここまで処理が実行されます。つまり、処理を中断する必要がある場合に、これは手っ取り早く簡単に処理を中断する方法です。私も何度かこの方法が必要になりました。これがプログレスバーです。かなり基本的な実装ですが、カスタマイズ方法はたくさんあります。そこから情報を取得したり、何でもできます。
次にお話ししたいのは、少し複雑で、少し設定が必要になるかもしれません。ここにテーブルを用意します。大きなクラスのサンプルテーブルにランダム化されたデータを入れて、仮想結合機能のデモを行いました。仮想結合についてよく知らない方のために説明すると、標準的な結合があります。これは…大きなスコアのクラスがあります。これには生徒ごとに複数のエントリがあります。生徒は10回何かに挑戦し、スコアを獲得しました。そこには生徒の名前、挑戦回数、スコアしかありませんが、他にもたくさんの情報があります。これらをここに取り込みたいので、結合を行います。JMPを使っている人なら誰でもやったことがあると思います。
これでこれらの値がこのテーブルに取り込まれますが、別の方法もあります。これらの名前はすべて一意です。これらをリンクIDとして設定し、このテーブルでそれを使ってこのテーブルを参照できます。これで仮想結合が確立され、すべての情報を確認できます。ここに戻って非表示/表示を切り替えると、結合を行わなくても、このテーブルのこの列の情報がこのテーブルからアクセスできるようになります。このテーブルでも同じことができます。
これにはかなり優れた利点があります。列情報とリンク参照プロパティを開くと、選択状態または除外状態を受け入れるように設定できます。これを適用してここに来て、この行を選択すると、関連する行がすべて選択されます。サマリーを作成し、データテーブルまたはデータビューにリンクした場合も同様の効果が得られます。
これはかなりクールな機能です。去年あたりから使い始めたのですが、すぐにJSLスクリプトで自動化できる方法が欲しくなり、最終的にはリンクマネージャのようなものを書くことになりました。では、これらをクリアしましょう。リンクIDとリンク参照をクリアします。これで、リンクされていない通常のテーブルになります。
これからサンプルのリンクマネージャーを起動します。いくつか実行してみます。すべてのテーブルを設定し、リンクマネージャーを初期化します。この背後にあるコードについては後ほど説明します。
できました。エラーの内容は分かりませんが、リンクマネージャーの基本ウィンドウが表示されています。GUIは表示されていますが、まだリンクは設定されていません。使用できるAPIを使ってリンクソースを設定できます。これは基本的に、先ほどリンクIDを設定したのと同じになります。実行すると、リンクマネージャーが更新されていることがわかります。
ここに新しいGUIエントリがあります。これでテーブルの1つをリンクできます。新しいエントリができました。2つ目のテーブルをリンクできます。2つ目のエントリができたので、これらのリンク設定にアクセスして変更する手段ができました。
この1つのディスパッチ、つまり2つ目のテーブルが必要な場合は、選択と除外をすべて…つまり、ここで何かを選択すると、それがこちらに伝播されるように設定できます。これで何でも選択できました。これで、これらがすべて選択されているはずです。さらに、元のソーステーブルも選択されているはずです。
通常、これらの情報を更新したい場合は、右クリックして「列情報」を選択し、リンク参照に移動して「ディスパッチ」または「承認」を選択し、必要な変更を加えてから「適用」し、「OK」をクリックして別のテーブルに移動して変更を適用します。私はもう少し動的な、何が起こっているかを確認できるような仕組みを望んでいました。別のテーブルを用意したり、複数のソースを設定してこれら全てを実行したり、すべての設定がここにあるので、設定をオフにしたり、リンクを削除したり、といった操作をすべて行うことができます。
これは、多くのテーブルにまたがる大規模なデータセットを扱う際に特に役立つツールです。例えば、1つの大きなサマリーテーブルがあり、連続データに対してデータ処理を行っていて、データを関連付けたいけれど、巨大なテーブルを作るのは扱いにくくなりすぎるので避けたい、といった状況です。このツールは、こうした経験から生まれました。
さて、コードを見ていくと、少し複雑になりますが、一つだけはっきりさせておきたいのは、これらはどれも最適化されていないということです。多くの部分はまだ…いわば最初の実装段階と言えるでしょう。動作を確認しただけで、後戻りしてクリーンアップしていません。これからいくつか見ていく際には、この点にご留意ください。
ここで呼び出したinitialize関数を確認してみてください。これはオブジェクトが存在するかどうかを確認しているだけで、存在しない場合はオブジェクトを作成しています。改善の余地がある点の一つは、複数のプロジェクトを実行している場合に複数のリンクマネージャーを同時にアクティブにしたいと考えていたことです。これは便利かもしれませんし、そうでないかもしれません。しかし、このクラスは現時点では基本的にシングルトンとして実装されています。
必要に応じてさらにインスタンス化することもできますが、その機能は、後ほど説明する特定の機能については少し不完全です。しかし、基本的にリンクマネージャは、テーブルがどのようにリンクされているか、どのテーブルが何にリンクされているか、そしてリンクの現在の状態を定義する配列を管理しているだけです。リンクを変更したり、その他様々な便利な機能を実行するメソッドも備えています。
GUIについては、ここに実装したクラスが2つあります。リンク設定があります。これは、リンクマネージャウィンドウを表示できるかどうかです。どこにあるかって?ここです。
リンクマネージャーウィンドウを見ると、リンク設定は、ディスパッチの受け入れを変更したり、除外を選択したりできるボックスです。これが設定です。そして、タブ、リンクソースタブもその一つです。ソースにはタブが割り当てられます。これは、どの列をリンクできるか、何をその列にリンクできるかなど、様々なルールがあるためです。リンクソースを作成すると、リンクマネージャーはこれらのオブジェクトのいずれかを作成します。
他にも、テーブルが閉じるタイミングなどを確認するためのイベントの設定や、状況確認など、様々な処理が行われていますが、これらはすべて無視して構いません。肝心なのはここです。新しいリンクソースタブオブジェクトを作成し、テーブルを提供しています。
だいたいこんな感じです。GUIに追加されますが、おそらくここのどこかだと思います。ここにソースリストがあるので、これを追加します。これは単なるコンテナです。タブを取得する関数があるのがわかります。これは実際の表示オブジェクトです。これは基本的に多くのレプリケーションを処理しています。新しいソースを作成するたびに、新しいものも作成され、すべての機能が自動的に取得されます。
ソースタブを開くと、これも初期化されています。独自の表示オブジェクト、ページボックス、整列処理などがあり、ここにもリストオブジェクトがあります。ここには、必要なリンク設定がすべて含まれています。繰り返しますが、これはおそらく最適化されていない部分もありますが、少しだけ改善されていることがわかると思います。JMPでは、インターフェースに動的な動作を組み込むのが簡単になるかもしれません。私は他のいくつかの場所でこれを使用しました。
ここにリコールライブラリがあることに気づいた方もいるかもしれません。少し前に私が最初に始めたときに、もっと複雑な実装があると少し触れたのですが、実はこれです。これについてじっくりと掘り下げる時間はあまりないと思っています。本当はもっとじっくり掘り下げたいのですが、実はこれがJMPクラスを使って着手した最初のプロジェクトだったんです。残念ながら、これもまだ最初のドラフト段階と言えるかもしれません。ここには簡単に統合できるような機能がたくさんあるのです。
ここまで読んで、JMPのクラスを活用してより動的な動作を実現し、コードをもう少しすっきりさせる方法について考えていただけたら嬉しいです。というのも、以前、複雑で動的なUIコードを新しいウィンドウ関数の中に組み込むようなコードを見たことがあったからです。
新しいウィンドウに、あれやこれや、あれやこれやと機能やボタンが20個も並んでタブが埋まってしまうなんて、本当に大変です。同じ動作を実装するなら、もっとクリーンでオブジェクトベースの方法があるのに。メンテナンス性、拡張性、可読性も向上します。UIを実装する際にクラスを使うことには多くのメリットがあると思います。
リコールについてはここでは詳しく説明しません。ただ一つ付け加えるとすれば、これらのサンプルコードとリコールライブラリをすべて整理し、公開Gitリポジトリで提供できるように取り組んでいくということです。興味のある方は誰でもリポジトリを閲覧したり、クローンを作成したり、フォークしたり、自由に活用したりして、JMPクラスを自分のコードでどのように活用すれば効率化できるかを理解することができます。
残っているのは、スコープ設定について最初に話した点のいくつかを簡単に指摘することだけだと思います。例を見てきましたが、動的なGUIを実装するための構造をどのように構築していくかは分かりました。次は、何が問題で、どのように回避できるかを理解しましょう。良い例としては…ここで試してみましょう。
これらのうち1つを選ぶとすれば、先ほどお話ししたスコープの問題は、メソッドの呼び出し方法と、メソッドがどのスコープを継承しているかに関係しています。Here名前空間で作業する際には、いくつかの問題に遭遇しました。グローバル名前空間とメソッドの衝突も経験しました。基本的に、クラスで発生する可能性のあるスコープの問題を回避するために私が特定した確実な方法は、できるだけシンプルにするために、ローカル修飾子を使用することです。ここでは具体的な例を示しませんが、どのように実装するのが良いかを示す良い例です。私は次のようにフォーマットするのが好きです。
ここで明示的に定義する必要がある変数はこれだけです。ここでは、参照を作成するためのプロパティを解析して作成するために設定している変数です。このコードは無関係です。この変数は、ローカルとして定義されていないという理由だけで、別のスコープの変数を隠蔽する可能性があります。そして、このメソッドがコード内のどこで呼び出されるかによって、この変数が競合を引き起こす可能性があります。
クラスとのスコープ衝突を回避するために私が見つけた最もクリーンな方法は、ローカル変数を非常に明示的に記述することです。これらのメソッドのほとんどは、これらの問題を回避するための最良の方法を理解する前に書かれたため、基本的にすべてのメソッドに対してこの記述をする必要があると思います。問題を回避するのに役立つもう1つの方法は、明示的な名前空間識別を使用することです。
私がライブラリを作成するときは、基本的に新しいカスタム名前空間で作成し、すべての変数とメソッド、または変数と関数もその名前空間の一部として定義するようにしています。これにより、競合を回避できます。
それから、実はもう一つ、あまり良い例ではありませんが、ポリモーフィズムと継承に関する問題についても触れておきたいと思います。そうですね。それについては、おそらくRecallライブラリでお話しするのが良いでしょう。
あまり…実装の詳細を省き、問題点だけに焦点を当てたいと思います。ここまで見てきたことを踏まえると、このリコールパネルは基本的に、フィールドの値を自動的に呼び出すためのコンテナです。これは継承されることを想定しているため、子クラスでいくつかの動作を呼び出す必要があります。具体的には、このメソッドです。子クラスは独自のビルドメソッドを実装する必要があります。
さて、ここでポリモーフィズムのニュアンスが関係してきます。なぜなら、このメソッドを他のメソッドから呼び出そうとした場合、つまり親クラスでbuildメソッドを呼び出したいのに、そのbuildメソッドをオーバーライドする子クラスが存在する場合、buildメソッドは子メソッドに正しくディスパッチされないからです。バックエンドでのクラスの実装方法に何らかのニュアンスが関係していると考える理由については、私には説明できません。しかし、この問題を回避する方法はあります。別のライブラリで説明するのが一番良いと思いますが、残念ながら、そこまで詳しく説明する準備はできていません。
これについては詳しく説明することは避けて、最後に、ポリモーフィズム、つまり動作をオーバーライドする子クラスを実装できるという事実は、期待どおりに機能しない可能性があるとだけ述べたいと思います。継承はクラスのより便利な側面の 1 つだと思うので、これは少し残念です。
動作を継承することは可能ですが、継承した動作を親クラスで呼び出すのは少し複雑です。ここでは、そのように説明しようと思います。これらの資料をオンラインに公開する際に、それがうまくいかない理由と方法について、より具体的な例をいくつか含める予定です。繰り返しますが、ここではJunk JSLでクラスをUIデザインに組み込む方法の概要のみを説明したいと思います。
ということで、これ以上長々と話すのはやめておきます。繰り返しになりますが、私の名前はベン・バレットです。ご質問や、これらの実装例をもっとご覧になりたい方は、お気軽にご連絡ください。以上、ありがとうございました。