クラス¶
クラスとは¶
クラスはオブジェクトの性質や動作を定義しています。
画面を構成する部品(ボタン、ウィンドウなど)、印刷される文字及び、処理されるデータの各要素は、すべてクラスにより表現されています。
クラスは、プロパティ、メソッド、イベント、定数が定義されています。
オブジェクトはクラスのインスタンスです。
クラスにより定義されるメソッドやプロパティをCRSスクリプトで操作することで、オブジェクトの挙動を指示することができます。
プロパティ¶
プロパティとは、プロパティ名とプロパティ値の組で表現されるオブジェクトの属性で、プロパティの値によりオブジェクトの性質を決定する要素です。
プロパティの値を変更する事で、オブジェクトの見えかたや、ユーザの操作に対する反応が変わります。
変化の状態はどのプロパティを変えたかによって異なります。
プロパティはオブジェクトのクラスに応じた多くの種類があります。
またプロパティごとにアクセス制御が行われており、動的に変更ができるもの、読取専用のもの、作成時にしか指定ができないものなどがあります。
メソッド¶
メソッド(Method)は一種の関数です。オブジェクトは実行されるメソッドに応じた動作を行います。
Methodの呼び出し例
Form1.deleteChild(); /* Form1の内容を消去します。 */
Form1.get("/mainmenu.crs"); /* Form1にmainmenu.crsをロードします */
イベント¶
オブジェクトが何かに反応する場合、イベントが発生します。
イベントの種類や発生する契機はクラスにより定義されます。例えば、Buttonクラスの場合、ボタンを押下するとTouchイベントが発生します。
イベントは、「"on" + イベント名」の名前をもつFunction(イベントハンドラと呼びます)により捕捉することができ、パラメータにEventオブジェクトが渡されます。
イベントハンドラでは、イベントに応じた処理を記述することができます。
:
Button btn {
x = 10;
y = 10;
width = 120;
height = 30;
function onTouch(e) {
title = "Clicked!";
}
}
クラスを操作するオペレータ¶
クラスに関する操作を行うオペレータとして、instanceofとsuperが用意されています。
instanceofは、あるオブジェクトがあるクラスのインスタンスかどうかを確認するために利用します。
:
if (object1 instanceof TextBox) {
messageBox("object1はTextBoxです");
}
:
この例では、object1がTextBoxオブジェクトか、またはTextBoxを基にして定義したクラスのオブジェクトの場合にメッセージボックスが表示されます。
instanceofのクラス判定は派生関係を考慮して行われる点に注意してください。
CRSの全てのクラスは、Objectクラスからの派生ですので、object1 instanceof Objectはobject1が何らかのオブジェクトを指している場合、常に真となります。
superオペレータは、Function内だけで利用できるオペレータです。superにより派生元クラスのメソッドを呼び出すことが可能となります。
class ClassA extends String {
function toString() {
return "ClassA : " + super toString();
}
}
class ClassB extends ClassA {
function toString() {
return "ClassB : " + super toString();
}
}
var A = new ClassA;
var B = new ClassB;
A.value = "test";
B.value = "test";
print(A.toString(), "\n");
print(B.toString(), "\n");
実行結果
ClassA : test
ClassB : ClassA : test
この例では、B.toString()の呼び出しにより、ClassBで定義されているtoString()が実行され、superによりClassAのtoString()が呼び出されます。
ClassAのtoString()ではさらにsuper toString()を実行しているため、StringのtoString()が呼び出されています。
また、superは、具体的なFunction名を指定せずに関数形式で呼び出すことができます。
コンストラクタFunctionの中から関数のように呼び出すと、派生元クラスのコンストラクタを呼び出されます。
通常のFunction内から呼び出すと、派生元クラスの同名のFunctionを呼び出します。
class MyClassA extends String {
function MyClassA(arg) { ... (A)
:
}
}
class MyClassB extends MyClassA {
function MyClassB(arg) {
super(arg); ... (B)
:
}
}
この例では、MyClassBのコンストラクタからMyClassAのコンストラクタを呼び出しています。
クラスの定義¶
Biz/Browserに内蔵されているクラスを基にして、別のクラスをCRSプログラムで新しく定義(派生と言います)することができます。
新しく定義したクラスは、基のクラスの特性を受け継ぎますが、新しい特性を定義したり元の特性を変更することができます。
:
class CustCode extends TextBox {
width = 120;
height = 24;
maxLength = 12;
font = new Font("", 12, /*bold*/true);
function OnGetFocus(e) {
BgColor = Color.YELLOW;
}
Function OnLostFocus(e) {
BgColor = Color.STD;
}
}
この例では、TextBoxを基にしてCustCodeクラスを定義しています。
CustCodeクラス固有の動作として、フォーカスを受けると黄色くなり、フォーカスを失うと元に戻るTextBoxを実現することができます。
アプリケーション画面を作成する場合、画面ごとに入力欄を個別に定義するよりも、このように入力項目に対応したクラスを定義して、画面にはそのオブジェクトを配置したほうが開発効率とメンテナンス性が向上します。
CustCodeクラスの利用例
:
Form form1 {
:
CustCode cd1 {
x = 10;
y = 10;
}
:
}
:
ユーザ定義メソッド¶
クラス定義内に記述されたファンクションは、メソッドとして扱われます。
クラスに定義されるファンクションは、定義するクラス単位に管理されます。
例えば、クラスAから派生したクラスBの両方にファンクションFが定義されている場合、クラスAのファンクションFとクラスBのファンクションFは別のものとして共存します。
オブジェクトから同じ名前で定義されている、こうしたファンクションを呼び出すと、クラス階層上でもっとも上位に定義されたクラスのファンクションが実行されます。
class ClassA extends String {
function functionF() {
return "ClassA : " + value;
}
}
class ClassB extends ClassA {
function functionF() {
return "ClassB : " + value;
}
}
var A = new ClassA;
var B = new ClassB;
A.value = "test";
B.value = "test";
print(A.functionF(), "\n");
print(B.functionF(), "\n");
実行結果
ClassA : test
ClassB : test
このように最上位のファンクションが呼び出される動作は、呼び出し元のスクリプトの位置には関係なく、オブジェクトのクラス階層のみにより決定されます。
class ClassA extends String {
function printFunc(text) { ... (A)
print("ClassA.printFunc : ", text, "\n");
}
function functionA() { ... (B)
printFunc("ClassA : " + value);
}
static function SFunctionA() { ... (E)
printFunc("ClassA : static");
}
}
class ClassB extends ClassA {
function printFunc(text) { ... (C)
print("ClassB.printFunc : ", text, "\n");
}
function functionB() { ... (D)
printFunc("ClassB : " + value);
}
static function SFunctionB() { ... (F)
printFunc("ClassB : static");
}
}
var C = new ClassB("test");
C.functionA(); ... (X)
C.functionB(); ... (Y)
ClassA.SFunctionA(); .... (Z)
ClassB.SFunctionB(); .... (W)
実行結果
ClassB.printFunc : ClassA : test
ClassB.printFunc : ClassB : test
ClassA : static
ClassB : static
この例では、(X)のfunctionAの呼び出しにより、(B)と(C)のファンクションが実行されます。
(Y)のfunctionBの呼び出しでは、(D)と(C)のファンクションが実行されます。
(Z), (W)では、それぞれClassA, ClassBで定義したスタティックなファンクションが実行されます。
(X),(Y)については、ClassAを派生させたClassBによりClassAのprintFuncを差し替えるように動作します。
また、次のようにClassBを派生させてClassCを定義してprintFuncを再定義すると、さらに別の動作を与えることができます。
class ClassC extends ClassB {
function printFunc(text) {
//.messageBox("ClassC.printFunc : " + text);
}
}
var C = new ClassC("test");
C.functionA();
実行結果
ユーザ定義プロパティ¶
クラス定義内では、そのクラスに対するプロパティ定義が記述可能です。
class ClassA extends Object {
# 読取専用プロパティ
default property propA { # "default"指定を行うことで、デフォルトプロパティをオーバーライド可能です
# プロパティgetter : 値を取得する際に呼び出されます
get {
return 123;
}
}
# 読み書き可能なプロパティ
property propB {
# プロパティgetter : 値を取得する際に呼び出されます
get {
# getter, setter共に、ブロック内には複数のステートメントが記述可能です
print("accessing ClassA.propB");
return _b;
}
# プロパティsetter : 値を設定する際に呼び出されます
set(new_value) { # 変数の名称は関数の引数と同じ規則内で自由に設定可能です
# 範囲チェックのようなコードも記述できます
if(new_value < 0) throw new Exception("USER", 1, 1);
_b = new_value;
}
}
# プロパティ値を保存するための子オブジェクト
Number _b;
}
ユーザ定義プロパティは、内蔵クラスのプロパティと同様に振る舞います。
var a = new ClassA;
print(a.propA); # ユーザ定義プロパティpropAを読み取ります
print(a); # propAがデフォルトプロパティ指定されているため、propAを読み取ります
try {
a.propB = -1; # propBのsetterで範囲チェックを行っており、例外が発生します
}catch(e) {
print("範囲外です");
}
なお、ユーザ定義プロパティににはいくつかの制約があります。
後述の「スクリプトにより定義されたクラスの制約」を参照して下さい。
コンストラクタ¶
全てのクラスは、コンストラクタと呼ばれるクラス名と同じ名前を持った初期化専用のメソッドを持っています。
コンストラクタは、クラスのインスタンス生成時に自動的に呼び出されます。
コンストラクタがどのような動作を行うか、また、どのような引数を採るかはクラスにより異なります。
例えば、Stringクラスのコンストラクタに文字列を渡すと、その文字列でStringオブジェクトが初期化されます。
var v = new String("test");
print(v, "\n");
実行結果
test
オブジェクトの初期化以外のコンテキストでコンストラクタを呼び出すことはできません。 例えば以下のようなスクリプトはエラーとなります。
var v = new String();
v.String("test");
print(v, "\n");
クラスを定義する際、コンストラクタは通常のファンクションと同じように定義しますが、ファンクション名をクラス名と同じ名前にします。
コンストラクタ定義の例
class MyString extends String {
function MyString(text) {
super(text);
value = ">>" + value + "<<";
}
}
コンストラクタを定義しない場合、派生元クラスのコンストラクタと同じコンストラクタが定義されたように振舞います。
class MyString extends String {
function MyString(text) { ... (A)
super(text);
value = ">>" + value + "<<";
}
}
class MyString2 extends MyString {
:
(コンストラクタ ファンクション未定義)
:
}
:
var c = new MyString2("test"); ... (B)
この例の場合、(B)のnewで呼び出されるコンストラクタは(A)となります。
コンストラクタの引数はnew演算子によるオブジェクトが生成と、super()ステートメントによる派生元クラスのコンストラクタ呼び出しで指定することができます。
var v = new MyString("hello");
コンストラクタが呼び出された場合、必ず派生元クラスのコンストラクタをsuper()ステートメントにより呼び出す必要があります。
class MyString extends String {
function MyString(text) {
super(text);
value = ">>" + value + "<<";
}
}
通常は、コンストラクタの先頭でsuper()を実行するようにしてください。
コンストラクタにsuperステートメントが含まれない場合、自動的に引数なしでsuperを呼び出す処理が先頭に挿入されます。
superステートメントを含むコンストラクタが、例外や制御文により派生元のコンストラクタを呼び出さずに復帰した場合、オブジェクトの生成を中断し初期化途中のオブジェクトは破棄され、例外がthrowされます。
初期化ブロックとonInitializedメソッド¶
ユーザ定義クラスの定義文において、"{" と"}"で括られたブロックのことを初期化ブロックと呼びます。
初期化ブロックは後述のように、インスタンスの生成時に呼び出されます。
namespace MyClass extends Number {
# この波括弧内の、プロパティ/メソッド/コンストラクタ定義を除いた箇所が初期化ブロックです。
...
# 初期化ブロック内
...
value = 123; # 初期化ブロック内 : 初期化ブロック実行時に代入
Number num1; # 初期化ブロック内 : 初期化ブロック実行時に作成されます
String str1; # 初期化ブロック内 : 初期化ブロック実行時に作成されます
function func1() {
# 初期化ブロック外
}
...
# 初期化ブロック内
...
}
初期化ブロック内では通常、プロパティを初期化したり独自の子オブジェクトを初期化および生成などを行います。
この初期化ブロックが実行されるタイミングはインスタンスの構築中であり、この時点においてはこのインスタンスは オブジェクトツリーに接続されていません。
そこで、オブジェクトツリーに接続していないと行えない処理を記述するためにonInitializedを使用します。
namespace MyButton extends Button {
...
function onInitialized() {
# 初期化中、オブジェクトツリーに接続された際に呼び出されます
BgColor = ^.BgColor; # オブジェクトツリーに接続されているため、親オブジェクトを参照可能です
}
...
}
onInitializedメソッドはユーザ定義クラス内のメソッドとして定義されたときのみ効果があります。
オブジェクト生成 時などにユーザ定義関数としてonInitialized関数を定義しても初期化時に呼び出されることはありません。
インスタンス生成時の動作¶
パッケージとクラス定義内のスクリプトの実行契機と変数スコープが通常のCRSスクリプトは異なることに注意してください。
01: namespace Package1 {
02: Number Pdata1 = 10;
03: String Pdata2 = "sample " + str(Pdata1);
04: class MyClass extends String {
05: MyData Cdata1 = Package1.Pdata1;
06: String Cdata2 = "sample " + str(Cdata1);
07: function MyClass(t, d1, d2) {
08: super(t);
09: Cdata1 = d1;
10: Cdata2 = d2;
11: }
12: function onInitialized() {
13: print("MyClass : parent.className = ", ^.className);
14: }
15: function FunctionA() {
16: print(value, Package1.Pdata1 , Cdata1, Cdata2, "\n");
17: }
18: }
19: class MyClass2 extends MyClass {
20: Number Cdata3 = 20;
21: function MyClass2(t) {
22: super(t, 20, "hello");
23: }
24: function onInitialized() {
25: super();
26: print("MyClass2 : parent.name = ", ^.name);
27: }
28: }
29: }
このようなパッケージをimportした場合、次のように動作します。
パッケージ内のオブジェクト定義部分 (2行目から3行目)
パッケージのロード時に実行されます。
この部分の変数スコープはPackage1です。 従って、Pdata2の初期値の設定にPdata1を使う事ができます。
クラス定義部分( 4行目から28行目)
パッケージのロード時点ではコンパイルとクラス名の登録が行われます。 実行は行われません。
クラス定義内に文法的なエラーがある場合にはロード時に検出されますが、 更新するオブジェクトが見つからないなどの実行時エラーは、MyClassのインスタンス生成時まで検出されないことに注意してください。
5行目でMyDateという未定義のクラスが使用されていますが、コンパイル時にはクラスの有効性は検査されないため、 MyClassの最初のインスタンス生成時までにMyDataクラスが定義されればエラーとはなりません。
この部分の変数スコープは、クラスのインスタンスとなります。 従って、インスタンス生成の状況により、オブジェクトツリーに関連するスコープが変化することに注意してください。
15行目で、2行目で定義されているPdata1の参照を期待していますが、実際にどのオブジェクトが参照されるかは実行時の状況により不定となります。
/* パッケージ名と同じ名前のオブジェクトの影響2 */
String Package1 {
String Pdata1 = "ABC";
MyClass C;
}
:
Package1.C.FunctionA();
この場合、FunctionAを実行中のthisはPackage1.Cとなり、FuntionA内からPackage1を参照すると、 パッケージ名のPackage1ではなく、StringのPackage1が参照されます。
その結果、Pdata1は、2行目で定義されたNumberオブジェクトではなく、StringのPdata1が参照されます。
また、次の場合、
/* パッケージ名と同じ名前のオブジェクトの影響2 */
String foo {
MyClass C {
String Package1;
}
}
:
foo.C.FunctionA();
変数スコープの原点であるCの直下にあるPackage1が検索ルールに従って優先的に検索されるため、 Stringのfoo.C.Package1.Pdata1が参照されますが、未定義となります。
このように、クラス定義内からクラス外部のオブジェクトを参照する場合、スコープ原点がインスタンスになる点に特に注意が必要です。
パッケージ名よりも子オブジェクトや先祖のオブジェクトの検索が優先される仕様により、 今回の例で期待したようにMyClass.FunctionAから、Package1.Pdata1を確実に参照する方法はありません。
パッケージに定義されているクラス、MyClass2のインスタンスを生成する場合、以下のように実行されます。
1. クラスのインスタンスが生成されます。
2. クラスの初期化ブロック(ブレース内にインラインで記述された部分)が、派生元から派生先 (String→MyClass→MyClass2)の順に実行されます。
3. 派生先から派生元 (MyClass2→MyClass→String)の順にコンストラクタを検索し、最初に見つかったコンストラクタが呼び出されます。
4. [オブジェクト定義式の場合] オブジェクトがオブジェクトツリーに接続されます。
5. [オブジェクト定義式の場合] 派生先から派生元 (MyClass2→MyClass→String)の順にonInitializedメソッドを検索し、最初に見つかったonInitializedメソッドが呼び出されます。
以下、呼び出しシーケンス順に復帰します。