GUIコンポーネントとインターフェース

[id:lethevert:20050829:p5]

# ytqwerty 『そうは言ってもVCLは(RAD情報をそれで再生している以上)ストリーミングができること第一で作られてますし、そこに文句をつけてもしょうがないと思われます。
あとは…まあ…Swingはlethevertさんの言うようになってますし、一見それがもっともに思えますけれど、Javaで閉じているSwingとは異なり、VCLは所詮OSのGUI部品へのラッパーで、下位のWindowsコントロールからしてデータをコントロール側で保持する実装ですので、外から与えたデータを直に使わせる形ですと、二重管理でメモリがもったいないとか変更があったときの同期どうすんだとか色々あるのではないかと。(それでデータ項目増やさないといけないとなったら杏さんのコメントも現実に問題になりうる…ってのは深読みし過ぎですか)
TStringsはそもそも、TListItemsのようなデータ本体が外部にあるものとTStringListのような単なる入れ物とを混ぜて使えるようにするための抽象基底クラスですし。
(TTreeNodeにも同様にツリービューと関係なくツリー構造を表す抽象基底クラスが必要と思われるのに無い、というなら私もそう思いますがここでは別の話)
同じJavaでもOSのコントロールを使うSWTVCL同様項目を生成後追加する形になってるみたいですしね。』 (2005/08/31 20:24)

インターフェースを使うことと、シリアライズできないことの関連がいまいちよくわからないです。あのコメントではちょっと分かりづらかったですが、僕がデザインセンスに注文をつけているのは、どちらかというと、Create(Owner: TComponent);の方なんですよね。インターフェースを使っても、シリアライズすることはできなくないと思うのですけど、なにか大きな見落としをしていたりします? ← 見直して、自分でも意味がわからないので、下に書き直し。
インターフェースを使っても、シリアライズ可能なような設計をすることは、難しくないのではないですか? TreeViewのノードをデザイナで静的に設定したいこともあれば、実行時に動的に生成したいこともあるので、動的の場合は、ノードなしの状態でシリアライズしておくぐらい可能でしょ? デザイナで設定したければ、Nodeインターフェースをインプリメントしたノードコンテナを別コンポーネントにして、TreeViewのプロパティにそのコンテナを選択しておくだけでいいと思うのですが。
昨日のコメントで注文をつけているのは、そういうところのデザインセンスなので、シリアライズすることそのものには問題ないと思います。Delphiのデザイナが使いにくいと思っているわけではないので。Javaのように、全部コードではなく、シリアライズしてロードする方が書きやすいし、見やすいと思いますし。(ただ、反面、自作コンポーネントを作ると、いろいろ気を使わないと、デザイナが誤動作して困ることもありますが)
2重管理でメモリがもったいないというのは、そんなことはないんじゃないですか、と思います。Windowsのコントロールは実はあまりよく理解していないですが、保持する必要のあるデータって、文字列と関連データへのポインタくらいだと思うので、そんなところをケチるくらいなら、柔軟性のある構造にする方がメリットが大きいと思いますよ。もし、そこまでケチりたいなら、Windows APIを直接呼べばいいわけですし、Delphiはそれが呼べるんですし。それに、インターフェースを利用する方法の方が、オブジェクトのコピーを行わなくてもよいので、逆にメモリの節約になることもあるように思います。
たとえば、インターフェースを使うとどういうことが便利かというと、たとえば、TreeViewで次のような問題を簡単に扱えます。

ネットワーク構造のデータに対して、探索の起点を与えて、起点からの経路を木構造として表示する

ネットワーク構造のデータは、下のようなデータとして表せます。

class NetworkNode
{
  NetworkNode[] neighbors;
  String label;
}

これに対して、ルートからのパスを持って、ネットワーク構造を探索する、次のようなクラスを用意するだけで、表示できます。(キャストとか省略しています。あと、先のインターフェースだと、ちょっとメソッドが不足しているようです。)

class NetworkWalker implements Node
{
  Stack nodepath;
  int currPos;
  Node getChild() {
    nodepath.push(nodepath.top().neighbors[currPos]);
    currPos = 0;
    return this;
  }
  Node next() {
    currPos++;
    NetworkNode node = nodepath.pop();
    if(nodepath.isEmpty() ||
       nodepath.top() <> node.neighbors[currPos]) {
      nodepath.push(node);
      return this;
    }else{
      nodepath.push(node);
      return next();
    }
  }
  Node getParent() {
    NetworkNode node = nodepath.pop();
    for(int i=0; i

もちろん、既存のVCLでも、このWalkerクラスから、DelphiのTTreeNodeオブジェクトを生成するクラスをもう一つ作ればいい話なんですが、それはライブラリに吸収できると思わないですか? そして、それを吸収する仕組みが、インターフェースじゃないですか?
それに、上のような書き方をすると、アクセスしたときにオブジェクトを生成するということが、イベントを使うこともなく、ごく自然に記述できることもメリットです。

      • -

眠いときに書いたから、ところどころ間違ってるな。意味のわからないところもあるし・・・
議論の本質ではないですが、上のコードだと、昨日のTreeViewの実装では、ちょっとまずいかも。木のルートは、メンバデータではなくて、getRoot()のようなメソッドから取得するようにしておかないと、正しくルートが取得できないですね。
とにかく、インターフェースというのは、形式さえ整えれば、実装は問わないというところに醍醐味があるので、表示したい元のデータがどういう形をしていても、必要なメソッドさえそろえれば、描画コンポーネントには何も手を加えずに描画できるということを主張したいのです。つまり、インターフェースを使っている方が、データと描画がより適切に分離された形でコーディングできるということです。
また、元のデータオブジェクトが、データの構造の問題で、継承を利用していても、多重継承なんてことを考えるまでもなく、インターフェースを実装するだけなので、追加コードが少ないということもあります。しかも、昨日の例でもわかるように、インターフェースで要求されるメソッドは、描画に関するメソッドよりもデータの操作に関するメソッドに近いので、既存のコードを流用しやすいということもあります。