以下はおおむね正しいです。何かを見逃したか、何かを誤解した可能性があります。リンクされたドキュメントを読んで(短すぎます)、コメントで発砲してください-ありがとう。
例えが悪い: クラスはクッキー カッターです。 インスタンスは、クッキー カッターから作成されたクッキーです。
類推の問題: JSLのも呼んでみますクラスaプロトタイプ. プロトタイプは実際のクッキーです。 試作クッキーできる食べたり変更したりできますが、通常はそうしたくないでしょう。 代わりに、その輪郭をクッキー生地にトレースし、それをハサミでカットして、クッキーの新しいインスタンスを作成する必要があります. しかし、プロトタイプ Cookie を噛むと、将来のすべての Cookie インスタンスも噛まれたように見えます (2 番目の例)。 また、プロトタイプのクッキーは _init_(...) メソッドを実行していないので、中途半端かもしれませんが、形は整っています。
のドキュメンテーション用途テンプレートそれよりもプロトタイプ. 私はテンプレートよりもプロトタイプを好みます。プロトタイプは使用可能なオブジェクトですが、テンプレートはスプレー塗料がどこに行くべきかを示しているだけです。 この微妙な違いは、最初の例では問題になりません。
簡単な例
これは単純な単一クラスの例ですバブル. クラスのプロトタイプは、DefineClass() によって作成されます。 NewObject() が作るプロトタイプからの 61 の実際のインスタンス。 61 個のインスタンスのそれぞれに、半径と x、y 中心があります。 プロトタイプは、_init_(...)、drift()、および draw() へのメソッドも提供します。 オブジェクトを使用してグラフを表示各オブジェクトの draw メソッド。 次にループが実行され、各オブジェクトのオブジェクト ドリフト メソッドが呼び出され、グラフに再描画が指示されます。 結果は、次のようなアニメーション グラフになります。
グラフのJSL:
// define a simple class (like a template) to represent a 2D bubble
Define Class("bubble",
// class member variables
m_radius = .; // class member variables are specified in
m_xpos = .; // the class definition with initial values.
m_ypos = .; // these values are copied into instances created by NewObject(...)
// class methods
_init_ = Method( {radius, x, y}, // called by NewObject(bubble(r,x,y)) below to initialize an instance
this:m_xpos = x; // this: is the namespace of an instance
m_ypos = y; // you don't need "this:" unless the member name is
m_radius = radius; // the same as a parameter name
);
drift = Method( {}, // a method to move the bubble
m_xpos = m_xpos * 0.995;// tentatively drift to center
m_ypos = m_ypos * 0.995;
);
draw = Method( {}, // a method to draw the bubble
Circle( {m_xpos, m_ypos}, m_radius )
);
);
// make a bunch of instances of the bubble class, keep them in a list
bubblelist = {};
For( i = 1, i <= 61, i += 1,
Insert Into( bubblelist,
// each instance of bubble is initialized with a random radius and position
New Object( bubble( Random Uniform( .03, .8 ), Random Uniform( -.9, .9 ), Random Uniform( -.9, .9 ) ) )
)
);
picno = 0; // need an ID for each picture
// make a window to display all the instances of the bubble class
New Window( "bubble class instances",
gb = Graph Box( // the graphic script for the graph box scales and sizes the axes and...
xaxis( {Add Ref Line( 0, "Solid", "Black", "", 1 )} ),
yaxis( {Add Ref Line( 0, "Solid", "Black", "", 1 )} ),
framesize( 600, 600 ),
X Scale( -1, 1 ),
Y Scale( -1, 1 ),
// the graphic script tells each bubble to draw itself
For( i = 1, i <= N Items( bubblelist ), i += 1,
bubblelist[i]:draw()
);
text({-.9,-.9},char(picno)),
<<ONCLOSE></ONCLOSE>それがどのように機能するかについての私のメンタル モデル: カバーの下で、DefineClass() はすべての DefineClass() プロトタイプの非表示のリストを保持し、それらは次のように見えます。たくさんインスタンスのように。 これについては後ほど詳しく説明します。上記の例では、NewObject() 関数を介してそれらにアクセスしています。 NewObject() は、プロトタイプ オブジェクトの新しいインスタンスを複製し、_init_ メソッドを実行します。
なにこれ: ?
上記の例では、メンバー変数に次の名前を付けましたm_プレフィックス。 これは必須ではありませんが、どの変数がメンバー変数であるかを覚えておくため、およびパラメーター名とローカル変数名との衝突を防ぐために役立ちます。 しかし、私も使用しましたthis:m_xpos _init_() メソッドの割り当ての 1 つ。 のこれ:名前空間は「現在のインスタンスのメンバー変数」を意味します。 それでこれ:x = x;メンバー変数 x とパラメーター x に同じ名前を付けたい場合に使用できます。 m_あなたのコードを保守する次の人にとって、より簡単になります。
なにバブル: ?
別の名前空間がありますあなたがほとんど必要としないこと非表示リストの DefineClass() プロトタイプが見えると言った理由を示しますたくさんlike インスタンス -- クラス名は名前空間。 使用するバブル:m_radiusインスタンス メソッドからでも、プロトタイプの m_radius にアクセスします。 静的メンバー変数が必要だと思われる場合、これは作成する方法です...注意が必要です。
静的値のプロトタイプの使用
次の例では、静的な m_ID 変数を使用しています。 コメントが示唆するように、おそらく別のバリエーションを作成する必要がありましたが、これは「注意する場合」の警告を説明しているため、依然として役立ちます。 これが何が起こるかです(私のメンタルモデル、再び):
以下の _init_() が実行されると、bubble2 クラスはすべてのプロトタイプ コンテンツを新しいインスタンスに既にコピーしています。 新しいインスタンスには、プロトタイプの m_id のコピーがあります。 次に、_init_() はプロトタイプの m_id() を変更します。 次のインスタンスは、変更された値を取得します。 他のメンバー関数は、プロトタイプの m_id (bubble2:m_id) と this:m_id (または名前空間のない m_id のみ) のいずれかを選択する必要があります。 ここでは、インスタンス固有の整数のコピーである m_id を使用します。
ビデオでは、パズルの最後のピースが画面の下端の 5:15 に収まっています。 黒い円は、m_id 値を表示している bubble2 です。 彼らは m_id を使用して、すべての近隣を調査するときに自分自身を確実に無視します。
// twist is a scaling divisor for a shearing rotation that moves
// the outer edge faster.
// this value needs to get bigger with more beehive cells.
global:twist = 600;
// choose the size to make them fit, but it also interacts with twist
// because the distance from the center grows
global:bubblesize = 0.057;
// use a defined class to represent each cell in the beehive.
Define Class("bubble2",
m_radius = .; // in the video, all beehive cells have the same radius
m_xpos = .; // the beehive cell's position
m_ypos = .;
//
// this is answering the question "can a JSL Class have a static member variable?"
// this could have been done more intuitively for this example by passing the
// index of the creation loop to the _init_ code, but here it is...initialize the
// "static" value to 1...
m_id = 1; // keep reading to understand the two-faced nature of this variable...
// What actually happens: when newObject(bubble2(...)) makes a new instance, the _init_(...)
// is called.
// First, the m_id is cloned from the prototype to the instance. The first instance gets m_id==1.
// Second, the bubble2: namespace causes the prototype class to increment the
// prototype's m_id. The next instance gets the new value from the prototype, m_id==2.
// this is a little odd, coming from a C++ -like language; in the drift method below,
// "other:m_id != m_id" both m_id values are regular non-static instance members.
// if you want the static behaviour, use the bubble2:m_id approach (bubble2 is the class
// name and access the singleton in the prototype class.)
// Normally you *never* want to use the class name as a name space qualifier because
// you set up the class correctly and initialize via _init_( parameters ). And normally
// a static value is also a constant value, so using a copy is just fine. But if
// multiple instances need to communicate via a singleton static, AND you always use
// the namespace, this is a reasonable place to keep the static.
// Having written all that, I now dislike this particular example because it is using
// a confusing combination of bubble2:m_id and instance values of m_id.
_init_ = Method( {radius, x, y},
m_radius = radius;
this:m_xpos = x; // this: (or nothing) operates on an instance
m_ypos = y;
bubble2:m_id++; // bubble2: operates on the prototype. The next newobject(bubble2(...)) uses the modified prototype
);
distanceBetweenCenters = Method( {other},
/*return*/ Sqrt( (m_xpos - other:m_xpos) ^ 2 + (m_ypos - other:m_ypos) ^ 2 )
);
distanceBetweenEdges = Method( {other}, // negative: overlaps
/*return*/ distanceBetweenCenters( other ) - (m_radius + other:m_radius)
);
drift = Method( {},
// bubbles should pull to 0,0 AND move away from near bubbles
angle = ATan( m_ypos, m_xpos );
radius = Sqrt( m_ypos ^ 2 + m_xpos ^ 2 );
shear = global:twist; // also depends on number of circles
shrink = .99950;
// radius/shear creates a tiny amount of rotation, more on the edges. That rotational
// shearing helps the circles bounce about. /400 is not enough and /100 is too much.
// radius*shrink pulls all the circles to the origin. It is balanced by push, below.
x = Cos( angle + radius / shear ) * radius * shrink;
y = Sin( angle + radius / shear ) * radius * shrink;
// check neighbors
{neighbors, distances} = kdt << kNearestRows( 7, m_id );
tension = 0;
ntension = 0; // calculate a tension, or stress, for contour to use as a color
For( iN = 1, iN <= N Items( neighbors ), iN += 1,
i = neighbors[iN];
other = bubblelist[i];
If( other:m_id != m_id, // <<<<<<<<<<<<<<<< NOT the bubble2:m_id value !!!
dist = distanceBetweenEdges( other );
// if the dist is zero, it is perfect. if the other guy is far away, ignore him.
If( Abs( dist ) < global:bubblesize * 0.8,
tension += dist ^ 2; // this will be a RMS value at the bottom
ntension += 1;
);
If( dist < 0, //push
angle = ATan( m_ypos - other:m_ypos, m_xpos - other:m_xpos );
x += Cos( angle ) * Abs( dist ) ^ .4 * .05;
y += Sin( angle ) * Abs( dist ) ^ .4 * .05;
);//
, //
Beep()
);
);
m_xpos = x;
m_ypos = y;
If( ntension == 0,
ntension = 1;
tension += (global:bubblesize / 2) ^ 2;
);
Sqrt( tension / ntension ); // RMS
);
draw = Method( {},
Transparency( .8 );
Circle( {m_xpos, m_ypos}, m_radius, "FILL" );
Transparency( 1 );
Text( center justified, {m_xpos, m_ypos - m_radius * .2}, Char( m_id ) );
);
);
bubblelist = {};
n = 12; // how many "rings" of hex cells
m = -26 + 21 * n + 3 * (n - 3) ^ 2; // yes, I used JMP to get this formula
For( i = 1, i <= m, i += 1,
Insert Into(
bubblelist,
New Object( bubble2( Random Uniform( global:bubblesize, global:bubblesize ), Random Uniform( -.9, .9 ), Random Uniform( -.9, .9 ) ) )
)
);
// need a table for contour plot, and it will include 100 values in a circle around the hive
ring = 100;
dt = New Table( "tension",
Add Rows( m + ring ),
New Column( "x", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) ),
New Column( "y", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) ),
New Column( "tension", Numeric, "Continuous", Format( "Best", 12 ), Set Values( [] ) )
);
For( i = 1, i <= ring, i += 1,
angle = 2 * Pi() * i / ring;
dt:x[m + i] = 1.25 * Sin( angle );
dt:y[m + i] = 1.25 * Cos( angle );
dt:tension[m + i] = 0;
);
cplot = dt << Contour Plot(
X( :x, :y ),
Y( :tension ),
Show Data Points( 0 ),
Fill Areas( 1 ),
Label Contours( 0 ),
Transform( "Range Normalized" ),
Color Theme( "Green Yellow Red" ),
Specify Contours( Min( 0 ), Max( .01 ), N( 50 ) )
);
Report( cplot )[framebox( 1 )] << Frame Size( 1920, 1080 ) << maximizewindow( 1 ) << backgroundcolor( "black" );
Report( cplot )[framebox( 1 )] << xaxis(
{Format( "Fixed Dec", 12, 3 ), Min( -1.4*1920/1080 ), Max( 1.4*1920/1080 ), Inc( 0.5 ), Label Row(
{Show Major Grid( 0 ), Show Major Labels( 0 ), Show Major Ticks( 0 ), Show Minor Grid( 0 ), Show Minor Labels( 0 ), Show Minor Ticks( 0 )}
)}
);
Report( cplot )[framebox( 1 )] << yaxis(
{Format( "Fixed Dec", 12, 3 ), Min( -1.4 ), Max( 1.4 ), Inc( 0.5 ), Label Row(
{Show Major Grid( 0 ), Show Major Labels( 0 ), Show Major Ticks( 0 ), Show Minor Grid( 0 ), Show Minor Labels( 0 ), Show Minor Ticks( 0 )}
)}
);
// here is the script that makes the bubbles draw
Report( cplot )[framebox( 1 )] << addgraphicsscript(
Fill Color( "black" );
text Color( "white" );
pen Color( rgbcolor(64,64,64) );
For( i = 1, i <= N Items( bubblelist ), i += 1,
bubblelist[i]:draw()
);
hline(0);
vline(0);
);
cplot << onclose(
keepRunning = 0;
Close( dt, nosave );
1;
);
Wait( 0 );
//dir = "$temp\ClassWithStatic" || Char( global:twist ) || "\";
//Delete Directory( dir );
//Create Directory( dir );
//Write( Convert File Path( dir, "windows" ) );
picno = 0;
keepRunning = 1;
While( keepRunning,
xy = J( N Items( bubblelist ), 2, . );
For( i = 1, i <= N Items( bubblelist ), i += 1,
xy[i, 1] = bubblelist[i]:m_xpos;
xy[i, 2] = bubblelist[i]:m_ypos;
);
// the kdtable makes it faster to find near neighbors
kdt = KDTable( xy );
For( i = 1, i <= N Items( bubblelist ), i += 1,
dt:tension[i] = bubblelist[i]:drift();
dt:x[i] = bubblelist[i]:m_xpos;
dt:y[i] = bubblelist[i]:m_ypos;
);
Report( cplot )[framebox( 1 )] << inval << updatewindow;
picno += 1;
// Report( cplot )[framebox( 1 )] << save picture( dir || Char( (picno) + 1e6 ) || ".png" );
Wait( .01 );
);
上記の両方の例は、オブジェクトを表すデータ テーブルの行と、メンバー変数とメソッドの JSL ユーザー関数を表す列を使用して簡単に (おそらくもっと簡単に) 作成できます。 特にサブクラス化を行わずにクラスを使用すると、他の人が JSL のクラスに慣れていない場合、JSL を理解するのが難しくなる可能性があります。
関数といえば、DefineClass のドキュメントでも言及されています。 私の知る限り、それらはテーブルにほとんど何ももたらさないため、おそらく避けるべきです. クラスのすべてのインスタンスは、おそらくすべてのメソッドとすべてのメンバー変数の個別のコピーを取得していると思います。 Function() を定義し、それをメンバー変数に格納する場合は、それを (おそらく) 1 つのコピーだけでよりグローバルな名前空間にコピーするために別のものを作成しているだけです。 しかし、インスタンスが 1 つ (または非常に少ない) しかなく、パラメーター以外は何も認識しないクラスの関数が本当に必要な場合は、おそらくクラス内の関数が必要です。
最後の例に移り、いくつかのサブクラス化を行い、最後の名前空間を見てみましょう。素晴らしい:を探す必要がありました (JSL のコメントを参照してください)。
直感的な例: 基本クラスは車両であり、サブクラス (または派生クラス) はトラックと車です。 トラックは、基本クラスのナビゲーション メソッドを、住宅街を避けることを知っているものに置き換えるかもしれません。 車は HOV レーンを利用する場合があります。 車両のリストは、A から B に移動するように指示されると、それぞれが正しいことを行います。
クラスの定義には興味深い引数があります。ベースクラス(...) . 以下の例では、形が基本クラスになり、四角とサークル形状から導き出されます。 この例では、正方形と円のリストを作成して表示します。
two squares, two circles, two outlined, two filled2 つの正方形、2 つの円、2 つのアウトライン、2 つの塗りつぶし
以下の説明は、試行錯誤に基づいています。何かがどのように機能するかを予測するのに役立つモデルになるのに十分正しいと私は信じています。
なに素晴らしい: ?
派生クラスが作成されると、JMP はクラスの最後まで実行されます。ベースクラスチェーンして先頭に戻り、最初に基底クラスを作成し、独自の名前空間で使用します。 名前空間は内部的にリンクされているため、派生クラスは独自の名前空間を調べてメンバー変数を参照でき、変数が見つかるまで名前空間チェーンを基本クラスまでたどります。 の素晴らしい: namespace は、現在の名前空間を調べずに名前空間チェーンを 1 回スキップすることを意味します。 そのため、以下の JSL は、「不要な場合は、基本メソッドを呼び出すために super: を使用しないでください」と言っています。基本メソッドは派生名前空間にアクセスできないため、派生関数を呼び出すことはできません。
多重継承: 「インターフェース」を使用していて、名前の重複を避けている場合は、機能する可能性があります。 幸運を。 兄弟の基底クラスが上のモデルの名前空間のチェーンのどこに収まるかはわかりません。
// demonstrates JMP class inheritance and overriding a method
// and calling the super class. A circle and a square class
// are derived from a shape class. Explains when to NOT use super:.
//
// I found super: in https://github.com/sassoftware/jsl-hamcrest/blob/master/Source/Core/Matchers/EqualTo.jsl
// it isn't documented anywhere else, but works great for calling base class _init_
// Also, https://github.com/sassoftware/jsl-hamcrest/blob/master/Source/Core/Utils.jsl
// shows a "self" implementation idea. This is a bad idea because it creates a circular reference
// that must be cleared before the object is lost/deleted...or it will leak.
// avoid making members and methods in base and derived classes with the same name.
// _init_, _to string_, _show_ are exceptions, but they must not try to use a derived
// class member or method. (you would not normally want to, so no problem.)
//
// avoid needless upcast (super:); it cripples the base class because there is no downcast or
// virtual mechanism. Without the upcast, a baseclass function runs with access to the
// derived class members and methods. (see square's draw method calling docolor calling record.)
//
// it appears that the derived chain is searched *after* the current class; in a two class
// (base,derived) example that inverts the search order for members and methods when a
// derived class uses "super:" to call a base class method. If the base class calls a "virtual"
// function that exists in both base and derived, it won't get the derived one if "super:" was
// used but will if no scope is used. You only need "super:" typically when you want to call
// the base class method from an overridden method of the same name. If the base class method
// isn't calling back to derived via a virtual, everything is good.
// deep details
//
// the output shows lines like this:
// record square=square(shape=(s= x=30 y=30 r=5 s=4 c=yellow f=1))
// record circle=circle(shape=(s= x=30 y=60 r=10 s=99 c=green f=0))
//
// the square and the circle both get the right answer via different
// paths. Because of way draw() is overridden by square and not by circle,
// the initial call, objects[window:i] << draw(), is calling either a
// derived member or a base member. In both cases, "this" is for the
// derived member. in square's draw(), I made an error, now commented
// out: /*super:*/docolor(), that caused docolor to run with "this"
// for the base class, not the derived class. docolor then calls record().
// all three classes have record() members. The intent is to override the
// base class version ("never seen"). The error caused the base class
// version to run for square because "this" of docolor() was for the base class.
Define Class( "shape", // base class for both shapes
m_Xcenter = .;
m_Ycenter = .;
m_radius = .;
m_sides = .;
m_color = .;
m_filled = .;
_init_ = Method( {x, y, radius, sides, color, fill},
m_Xcenter = x;
m_Ycenter = y;
m_radius = radius;
m_sides = sides;
m_color = color;
m_filled = fill;
);
_to string_ = Method( {},
"shape=(s=" ||
" x=" || Char( m_Xcenter ) || " y=" || Char( m_Ycenter ) ||
" r=" || Char( m_radius ) || " s=" || Char( m_sides ) ||
" c=" || Char( m_color ) || " f=" || Char( m_filled ) || ")"
);
_show_ = _to string_;
// the following should NOT ever get called because square and circle override it
// try changing square to call super:docolor and then you'll see this
record=method({},show("never seen, overridden by square and circle"));
// both shapes understand filled vs outlined
// see note in square:draw method about calling docolor without super:
docolor = Method( {},
this:record(); // this: doesn't change anything,
If( this:m_filled, // it is mostly for disambiguating
Fill Color( /*this:*/m_color );// parameter of same name
, //
Pen Color( this:m_color )
)
);
// the base class has a draw method that circle uses;
// square provides an override because this draws a
// diamond-oriented square
draw = Method( {},
docolor();
p = J( m_sides, 3, 2 );//2==draw
p[1, 3] = 1;//1==move
p[m_sides, 3] = -2;//-2==draw and close
For( i = 1, i <= m_sides, i += 1,
p[i, 1] = m_Xcenter + m_radius * Cos( 2 * Pi() * i / m_sides );
p[i, 2] = m_Ycenter + m_radius * Sin( 2 * Pi() * i / m_sides );
);
Path( p, m_filled );
);
);
Define Class( "circle", baseclass( shape ),
_init_ = Method( {x, y, radius, color, fill},
super:_init_( x, y, radius, 99/*sides*/, color, fill )
);
_to string_ = Method( {},
"circle(" || super:_tostring_() || ")"
);
_show_ = _to string_;
record=method({},write("\!nrecord circle=",_show_()));
// circle has no override for draw()
);
Define Class( "square", baseclass( shape ),
_init_ = Method( {x, y, radius, color, fill},
super:_init_( x, y, radius, 4/*sides*/, color, fill )
);
_to string_ = Method( {},
"square(" || super:_tostring_() || ")"
);
_show_ = _to string_;
record=method({},write("\!nrecord square=",_show_()));
// square overrides the base class draw
draw = Method( {},
/*super:*/docolor();// don't up-cast, docolor can't down-cast and gets base class record()
Rect(
m_Xcenter - m_radius,
m_Ycenter - m_radius,
m_Xcenter + m_radius,
m_Ycenter + m_radius,
m_filled
);
);
);
objects = {};
Insert Into( objects, New Object( square( 30, 30, 5, "yellow", 1 ) ) );
Insert Into( objects, New Object( Circle( 30, 60, 10, "green", 0 ) ) );
Insert Into( objects, New Object( square( 60, 30, 15, "blue", 0 ) ) );
Insert Into( objects, New Object( Circle( 60, 60, 20, "red", 1 ) ) );
New Window( "objects",
gb = Graph Box( framesize( 500, 500 ),
For( window:i = 1, window:i <= N Items( objects ), window:i += 1,
//Show( objects[i] );
objects[window:i] << draw(); // uses the base class or the derived class draw
)
)
);
//stop();
// animate the color of object 4, the big red circle
For( i = 0, i <= 1, i += (1 / 256), //show(i);
objects[4]:m_color = HLS Color( i, .5, 1 );
gb << inval << updatewindow;
Wait( 0 );
);
どうですかこれ?
C++ には「this」という名前の値があります (他の言語では「self」と「me」を使用します)。これは、オブジェクトがそれ自体を参照するために必要になる場合があります。 これと同じではありません: 名前空間プレフィックス。 JSL クラスがクラス内で「これ」を提供するとは思わないので、次のようなものを作成したくなるかもしれません。
x = newObject(myClass(17));
x::self = x;
またはそれに近いもの。 次に、myClass メソッドは「self」を使用して、クローン作成など、クラス インスタンスを必要とすることを行うことができます。
Scripting index showing messages that can be sent to a class instanceクラス インスタンスに送信できるメッセージを示すスクリプト インデックス
クラス インスタンス (へのポインター) を含むクラス インスタンスにメンバー変数を保持すると、メモリ リークが発生する可能性があります。 これにより、新しいトピックが発生します...実際にはトピックです。
インスタンスはいつ削除されますか?
インスタンスへの参照がない場合、インスタンスは削除されます。 上記のセクションでは、インスタンス内の「自己」参照により、インスタンスが削除されなくなります。インスタンスへのすべてのハンドルを失ったとしても. JMPを閉じる以外に、クリーンアップする方法はありません。
インスタンスはいつコピーされますか?
ここが最良の部分です。インスタンスは <これは、インスタンスが参照によって渡されることを意味します。わーい! JMPに行列引数のコピーをパラメータ値に作成させ、場合によっては別のコピーを返す代わりに、単純なクラスを作成して大規模な行列をラップし、クラスのインスタンスをユーザー関数に渡す方が面倒ではありません。