2011年1月26日水曜日

Scala Swingでデザインパターン[Command]

また昔買った「デザインパターン入門講座」本のサンプルを実装してみました。

こちらのプログラムはCommandパターンのサンプルとして紹介されているプログラムで
  1. 「File」-「Open」からファイル選択ダイアログを開く
  2. 「File」-「Exit」からプログラムを終了する
  3. 「Red」ボタンを押下すると背景が赤になる
という3つの動作を行うだけです。

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

このサンプルを実装してみて思ったのですが、Scalaはこの方法でイベント処理を実装するのに不向きなような気がします。 サンプルで実装されているアクションの実行方法は、

  1. ボタン、メニューのコンポーネントオブジェクトにCommandオブジェクトを保持するためのインタフェースを追加する
  2. コンポーネントにCommandオブジェクトを追加する
  3. イベント発生時にイベントオブジェクトからイベント発生元のコンポーネントを取得する
  4. コンポーネントからコマンドを取り出し実行する

というものなのですが、4.のコンポーネントからコマンドを取り出すには、型キャスト(アンボクシング?)が必要になります。

ジェネリクスによる型認識を徹底的に駆使して、型付言語でありなが型を意識することなく実装していくことを意識して設計されているScalaなのに、型キャストをしなければいけないのはいかがなものかと思ってしまうわけです。

ScalaではScalaなりのアクションの実装方法があるような気がするんですが、それがなんだかは全く検討がつきません。

そのうち気付くかな?でも、そこまで Scala + Swing を追求していこうとも思っていなんですが・・・・。

型キャストはasInstanceOf[T]というメソッドで行うことができます。

実装例は下記のとおりになります。
reactions += {
  case ButtonClicked(a) => a.asInstanceOf[CommandHolder].command.execute
}
本サンプルのソースはこちら
import scala.swing._
import scala.swing.event._
import java.awt.Color
import java.awt.Dimension
import javax.swing._

object CommandSample extends SimpleSwingApplication {
  private val openMenu  = new CmdMenu("Open...")
  private val exitMenu  = new CmdMenu("Exit", new ExitCommand)
  private val redButton = new CmdButton("Red")
  
  def top = new MainFrame {
    title = "Command Sample"
    
    menuBar = new MenuBar {
      contents += new Menu("File") {
        openMenu.command = new FileCommand(this)
        contents += openMenu
        contents += exitMenu
      }
    }
    
    contents = new FlowPanel {
      preferredSize = new Dimension(150, 75)
      redButton.command = new RedCommand(this)
      contents += redButton
    }
    
    listenTo(openMenu)
    listenTo(exitMenu)
    listenTo(redButton)
    
    reactions += {
      case ButtonClicked(a) => a.asInstanceOf[CommandHolder].command.execute
    }
  }
}

/**
 * コマンド保持用トレイト
 */
trait CommandHolder {
  def command : Command
  def command_=(comd: Command)
}

/**
 * コマンドトレイト
 */
trait Command {
  def execute: Unit
}

/**
 * Commandを保持できるButtonのクラス
 * 
 */
class CmdButton(text0: String, comd0: Command) extends Button(text0) with CommandHolder {
  
  private var _command = comd0
  
  // 補助コンストラクタ
  def this() = this("", null)
  def this(text0: String) = this(text0, null)
  def this(comd0: Command) = this("", comd0)

  
  def command = _command
  def command_=(comd: Command) = { _command = comd }
  
}

/**
 * Commandを保持できるMenuのクラス
 * 
 */
class CmdMenu(text0: String, comd0: Command) extends MenuItem(text0) with CommandHolder {
  
  private var _command = comd0;
  
  // 補助コンストラクタ
  def this() = this("", null)
  def this(text0: String) = this(text0, null)
  def this(comd0: Command) = this("", comd0)
  
  def command = _command
  def command_=(comd:Command) = { _command = comd }
  
}

/**
 *
 */
class ExitCommand extends Command {
  def execute = System.exit(0)
}

/**
 * FileChooser表示コマンドクラス
 *
 */
class FileCommand(comp:Component, name:String ) extends Command {

  private val fc = new FileChooser
  
  fc.title = name
  
  // 補助コンストラクタ
  def this(comp: Component) = this(comp, "")
  
  def execute = {
    fc.showOpenDialog(comp)
  }
}

/**
 * 背景を赤くするコマンド
 */
class RedCommand(comp:Panel) extends Command {
  
  def execute = {
    comp.background = Color.red
    comp.repaint
  }
  
}

0 件のコメント:

コメントを投稿