RubyでDSLの真似事?をしています。Rubyの不勉強のため想定外の動き(解釈)にやっつけられていますが、そのたびにいい勉強(頭の刺激)になっています。で、DSL作りのときに思い出したのが、今年はじめに購入した本。内部DSLの言い出しっぺ?であるマーチン・ファウラー氏の章を読み返してみました。なるほど、なるほど。改めて読むと発見が多々ありました。1つはタイトルでもあるクラスメソッドやクラス変数の使い道の話。
内容はかなりはしょりますが、DSLを作るときには、まず始めにグローバル関数やグローバル変数を使ってみましょう。。。という話から始まって、グローバル関数やグローバル変数の数が多くなると管理が煩雑ですよね。。。だからクラスの中に入れちゃって、クラスメソッド、クラス変数として使いましょう、というくだりがあります。おお、そうか。。。グローバル関数などのスコーピング用にクラスを利用する手があるのか。。。という発見でした。
クラスメソッドといえばMathクラスのようなユーティリティクラスでの利用、クラス変数といえばシングルトンのように共通変数として利用、といった目的しか考えたことがありませんでした(お里が知れますね。。。お恥ずかしい限りで)。
そこで思い出したのがProcessingです。私はProcessingでアニメーションプログラムをささっと作るときには、まず素朴なクラスを作ってデータをセットし、インスタンスをコレクションクラスに放り込みデータ更新や表示に利用します。ごく普通の教科書的?なプログラムだと思います。例えば、以下のような感じです。
ArrayList list = new ArrayList(); class Point { float x, y, c; public Point(float x, float y) { this.x = x; this.y = y; } void draw() { fill(c); c=++c%255; ellipse(x, y, 20, 20); } } void setup() { size(200, 200); noStroke(); } void draw() { background(0); for(int i=0; i<list.size(); i++){ ((Point)list.get(i)).draw(); } } void mousePressed() { list.add(new Point(mouseX, mouseY)); }
いつも気になっていたのは上記プログラム中の変数listの存在でした。ちょっと複雑なプログラムになるとグローバル変数(ではないでけどね)の管理が煩雑になります。(前置きがいつもながら長い)で、上記をヒントにクラス変数にしちゃえばいいんじゃないの!?ということに気づいたわけです(大した話ではないんですけど。。。習慣が変わるという意味では嬉しい発見でした)。クラスメソッド&クラス変数に変更したのが以下です。おおー!setup()やdraw()が簡素になっていい感じです。。。しかし。。。残念ながらこのプログラムはエラーになります。
class Point { static ArrayList list = new ArrayList(); static void draw_all() { for(int i=0; i<list.size(); i++){ ((Point)list.get(i)).draw(); } } static void add(float x, float y) { list.add(new Point(x, y)); } float x, y, c; public Point(float x, float y) { this.x = x; this.y = y; } public void draw() { fill(c); c=++c%255; ellipse(x, y, 20, 20); } } void setup() { size(200, 200); noStroke(); } void draw() { background(0); Point.draw_all(); } void mousePressed() { Point.add(mouseX, mouseY); }
ProcessingではIDEで書いたプログラムはクラス定義も含めてメインクラスに包含されてしまいます。メインクラスがstaticでないので内部クラスだけstaticにはできませんよーというエラーが表示されます。しかたがないので、TABを追加してPoint.javaというタブを追加します。
■メインコード void setup() { size(200, 200); noStroke(); } void draw() { background(0); Point.draw_all(); } void mousePressed() { Point.add(mouseX, mouseY); } ■Point.javaタブ import java.util.ArrayList; class Point { static ArrayList list = new ArrayList(); static void draw_all() { for(int i=0; i<list.size(); i++){ ((Point)list.get(i)).draw(); } } static void add(float x, float y) { list.add(new Point(x, y)); } float x, y, c; public Point(float x, float y) { this.x = x; this.y = y; } public void draw() { fill(c); //エラー c=++c%255; ellipse(x, y, 20, 20); //エラー } }
上記でstatic関連のエラーは解消できるのですが。。。Pointクラスを独立させてしまった(PAppletのサブクラスではない)ため、Processingのメソッドが利用できません。。。上記のコードでいうfillメソッドやellipseメソッドです。もう少しがんばってみますと。。。じゃ、PAppletのポインタをPointクラスに渡してProcessingのメソッドを利用可能にしたのが以下です。
■メインコード void setup() { size(200, 200); noStroke(); } void draw() { background(0); Point.draw_all(); } void mousePressed() { Point.add(mouseX, mouseY, this); } ■Point.javaタブ import processing.core.PApplet; import java.util.ArrayList; class Point { static ArrayList list = new ArrayList(); static void draw_all() { for(int i=0; i<list.size(); i++){ ((Point)list.get(i)).draw(); } } static void add(float x, float y, PApplet p) { list.add(new Point(x, y, p)); } float x, y, c; PApplet p; public Point(float x, float y, PApplet p) { this.x = x; this.y = y; this.p = p; } public void draw() { p.fill(c); c=++c%255; p.ellipse(x, y, 20, 20); } }
ううーん。美しくない。。。クラスメソッド&クラス変数戦略の利用は、Processingでは時と場合によりそうです。。。