2011年1月20日木曜日

Swingでデザインパターン[Bridge]

「デザインパターン入門講座」本から「Bridge」パターンのサンプルをScalaで実装してみました。 今回は一つのデータを利用して、一般顧客用とエグゼクティブ顧客用の2種類のビューを生成するというものです。 コンポーネント追加用のメソッドを持つ抽象クラスをつくり、それを実装するクラスに委譲の形でコンポーネントを持たせています。

そして、コンポーネントもインタフェースをかませることで抽象度を高くしています。

Java実例プログラムによるデザインパターン入門講座―Swingプログラムで体得する23のパターン

個人的には、コンポーネント追加用の抽象クラスがBorderPanelを継承しているところがちょっと気になりました。 デスクトップアプリ(Swing)以外の方法で表示したくなったときにどうするのかな?と。「そこは、継承より委譲だろ」と思ってしまいました。Swing、デザパタに詳しい人とかはひょっとしたらもっと大きな問題に気付けたりするのかもしれないですが、自分ではこれが精一杯。

今回はまったのがずばりscala.swingパッケージにJTreeのラッパークラスがない!!ということでした。

なんで、JTreeのラッパーだけないんでしょう?実装漏れ?JTreeクラスに何か欠陥があるのか?「こんなコンポーネントいらね~よ」と実装者が思ったのでしょうか?まあ、考えてもしょうがないのですが・・・・。

かなり調べるのに時間がかかってしまったのですが、scala.swing.Componentクラスの中にコンポーネントを委譲させるように実装すれば言いということがわかりました。
実装例は下記の通り
viewportView = new Component {
    override lazy val peer = new JTree(root)
  }

あまりいいサンプルじゃないので、もう一つ

class Tree extends Component {
  override lazy val peer = new JTree(root)
}

遅延評価変数になっているところがミソ?なのかな・・・。

Scala Swing 独特のイベントリスナの実装も割と簡単にできるのですが、それはまた別の機会にということで(知りたい人はコメントとかくれるとありがたいです)。

あと今回、Javaのリフレクション機能を利用して文字列から動的にクラスを生成する方法を色々調べたのですが、結局わからずじまいとなってしまいました。Scalaという言語の特性上、基本的にやらないほうがいい処理なんでしょうか?
ちなみに、コップ本でもプログラム内で生成処理を分岐させて別のクラスの実装を返す方法しか紹介されていませんでした(27章)。

本サンプルのソースとサンプル内で利用したデータファイルはこちら
import scala.reflect._
import scala.swing._
import scala.io._
import java.awt.Dimension
import javax.swing.table._
import javax.swing.tree._
import javax.swing._

object Bridge extends SimpleSwingApplication {
  
  // ファイルの読込
  val prod = Source.fromFile("products.txt").getLines.toList
  
  def top = new MainFrame {
    title = "Bridge Sample"
    
    contents = new GridPanel(1, 2) {
      maximumSize = new Dimension(400, 400)
      
      // 左側のパネルを生成する
      contents += new BorderPanel {
        preferredSize = new Dimension(200, 200)
        layout(new Label("Customer View")) = BorderPanel.Position.North;
        val listBridge = new ListBridge(ListBridge.List)
        listBridge.addData(prod)
        layout(listBridge) = BorderPanel.Position.Center;
      }
      
      // 右側のパネルを生成する
      contents += new BorderPanel {
        preferredSize = new Dimension(200, 200)
        layout(new Label("Executive view")) = BorderPanel.Position.North;
        val listBridge = new SortBridge(ListBridge.Tree)
        listBridge.addData(prod)
        layout(listBridge) = BorderPanel.Position.Center;
      }
    }
    
  }
}

/**
 * データをコンポーネントに追加するメソッドを持ったインタフェース
 * (Abstraction)
 */
abstract class Bridger extends BorderPanel {
  def addData(data: List[String])
}

/*
 * 実装クラス郡を示す文字列定数を持たせる
 */
object ListBridge {
  val List  = "List";
  val Table = "Table";
  val Tree  = "Tree";
}

/**
 * データ追加メソッドの実装クラス
 * (RefinedAbstraction)
 */
class ListBridge(str: String) extends Bridger {
  
  protected val list = str match {
    case "List"  => new ProductList
    case "Table" => new ProductTable
    case "Tree"  => new ProductTree
    case _       => new ProductList
  }
  layout(list) = BorderPanel.Position.Center
  
  // data内の全ての値をlistに追加する
  def addData(data: List[String]) = data.foreach(list.addList(_))
}

/**
 * データをソートしてから追加メソッドの実装クラス
 * (RefiendAbstractionその2)
 */
class SortBridge(str: String) extends ListBridge(str) {
  override def addData(data:List[String]) = data.sortWith(_ < _).foreach(list.addList(_))
}

/**
 * コンポーネント郡のインタフェース
 * (Implementer)
 */ 
trait VisList {
  def addList(str: String)
  def removeLine(num: Int)
}

/**
 * コンポーネントの実装。JListによる実装
 * (ConcreteImplementer)
 */
class ProductList extends ScrollPane with VisList {
  
  private val list = new ListView[String]
  list.prototypeCellValue = "Abcdefg Hijkmnop"
  viewportView = list
  
  def addList(str: String) = {
    list.listData :+= str.split("--")(0);
    revalidate()
  }
  def removeLine(num: Int) = list.listData = list.listData.drop(num)
}

/**
 * コンポーネントの実装。JTableによる実装
 * (ConcreteImplementer)
 */
class ProductTable extends ScrollPane with VisList {
  private val table = new Table {
    model = new DefaultTableModel(0, 2)
  }
  viewportView = table;
  
  def addList(str: String) = {
    val splitStr = str.split("--")
    val itemList = new java.util.Vector[String]
    splitStr.toList.foreach(itemList.add(_))
    (table.model.asInstanceOf[DefaultTableModel]).addRow(itemList)
    revalidate()
  }
  def removeLine(num: Int) = null
}

/**
 * コンポーネントの実装。JTreeによる実装
 * (ConcreteImplementer)
 */
class ProductTree extends ScrollPane with VisList {
  private var root = new DefaultMutableTreeNode("Product")
  viewportView = new Component {
    override lazy val peer = new JTree(root)
  }
  
  def addList(str: String) = {
    val splitStr = str.split("--")
    if (splitStr.length > 1) {
      var base = new DefaultMutableTreeNode(splitStr(0))
      root.add(base)
      base.add(new DefaultMutableTreeNode(splitStr(1)))
    }
    else {
      root.add(new DefaultMutableTreeNode(str))
    }
    
    revalidate();
  }
  def removeLine(num: Int) = null
}
データファイル
Brass plated widgets --1,000,076
Furled frammis       --75,000
Detailed rat brushes --700
Zero-based hex dumps--80,000
Anterior antelope collars --578
Washable softwear --789,000
Steel-toed wing-tips --456,666

0 件のコメント:

コメントを投稿