ネームスペースについて本気出して考えてみた
前回の続きです。
ネームスペースについて考えてみましたが、いまいちピンとくる使い道が分かりません...。
僕が使いそうな範囲で、ネームスペースを使う意味を並べてみます。
- メンバに対するアクセス制限
- 動的なクラス内でプロパティやメソッドの名前が衝突しないようにする
ぐらいでしょうか。特に大事に感じるのは前者です。
例えば、親子関係の登録したりするクラス(例えばDisplayObjectやDisplayObjectContainerみたいな)を作った場合の子供が持つプロパティである「parent」を実装する場合、通常この「parent」は読み取り専用です。要はどのようにして「parent」が設定されているのか。「DisplayObject」の「parent」は、「DisplayObjectContainer」の「addChild」メソッド内で、internalな「setParent」的なメソッドで設定されている気がします。「DisplayObject」と「DisplayObjectContainer」は同じパッケージ内なので、「DisplayObject」の「parent」は実質使う側は読み取り専用として機能させることができそうです。
では、ある事情で「DisplayObject」と「DisplayObjectContainer」が違うパッケージにある場合、子の「parent」はどのように設定させればいいのでしょうか。
今回は例として子を登録させる「Parent」、親に登録する「Child」クラスを考えてみます。「Parent」クラスのパッケージは「test.parent」、「Child」クラスのパッケージは「test.child」としておきます。ついでに、「Child」クラスの「parent」の設定のアクセスを規制するネームスペース、「access」の定義ファイルも「test.child」に入れておきます。
つまり
test ┣ child ┃ ┣ access.as ┃ ┗ Child.as ┗ parent ┗ Parent.as
まずはネームスペースの定義です。
package test.child { public namespace access = "test/child"; }
続いて親に登録する子クラス。
登録した親を指す「_parent」があります。getterはpublicですが、setterはネームスペースに閉じ込めています。getterとsetterで名前が違うのは、getterとsetterにアクセスした際の曖昧さを回避するためです。
package test.child { import test.child.access; import test.parent.Parent; public class Child { private var _parent:Parent = null; access function setParent(p:Parent):void { _parent = p; } public function get parent():Parent { return _parent; } } }
そして子を登録する親クラス。
子を登録するメソッド、「setChild」が定義されています。ネームスペースを指定して、「setParent」で子の「_parent」を自分に設定しています。
package test.parent { import test.child.access; import test.child.Child; public class Parent { private var _child:Child = null; public function setChild(c:Child):void { use namespace access; c.setParent(this); _child = c; } } }
では実際に動かしてみます。
var p:Parent = new Parent(); var c:Child = new Child(); trace(c.parent); p.setChild(c); trace(c.parent);
出力は
null
[object Parent]
親に登録する前は「parent」が「null」になっています。登録後は「parent」が「object Parent」になっています。正常に子の親が設定されました。
しかし本題は、この子の「parent」が読み取り専用であることです。試しに子の「setParent」を直接さわってみます。
var p:Parent = new Parent(); var c:Child = new Child(); trace(c.parent); c.setParent(p); // ←コレ p.setChild(c); trace(c.parent);
コンパイルするとエラーが出ました。
D:\Test\src\Test.as(15): col: 6 エラー: アクセスできないメソッ
ド setParent へのアクセスを、静的型 test.child:Child の参照を使用して試行しまし
た。
c.setParent(p);
実質、子の「parent」は読み取り専用となっています。めでたし!
しかし、ネームスペースでこのようなアクセスの制限をしたところで、accessをimportして、use namespaceすれば「setParent」を実行できてしまいます。
use namespace access; var p:Parent = new Parent(); var c:Child = new Child(); trace(c.parent); p.setChild(c); trace(c.parent); c.setParent(null); // 直接parentを設定 trace(c.parent);
出力
null
[object Parent]
null
少々気持ち悪いといいますか、完璧じゃあないですね...。ここがいまいちピンと来ない部分なのです。
しかし実際、importしてuse namespaceをするという作業は手間なので、アクセスしやすさは低下しているのも事実ですし、こういうものだと諦めるべきなのかもしれません。
もっと「setParent」をちゃんと隠せますよという方法があれば教えてください。