2011年1月18日火曜日

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

「デザインパターン入門講座」本のAbstract FactoryパターンのサンプルをScalaで実装してみました。 こちらは、左のラジオボタンで選択した庭園の種類に応じて、ボタンを押下したときに右上のパネルに表示される文字列が異なってくるというものです。
ラジオボタンに対応する庭園クラスがあるのですが、それぞれの庭園クラスの親クラスを実装し、イベントアクション内では親クラスを操作するという形になっています。Factoryクラスは直感的だから、そんなに説明も必要ないとは思いますが・・・・

Java実例プログラムによるデザインパターン入門講座―Swingプログラムで体得する23のパターン このサンプルは、特に困ることもなく実装できました。 強いて言うと、GridBagPanelクラスの中で利用するConstraintsオブジェクトをどうやって生成するのだろう?というところで、少しつまづいたぐらいでした。 結局GridBagPanelクラスの中のpairConstraintsメソッドから生成する方法ぐらいしかわかりませんでした。 まあ、こちらのメソッドの利用方法はAPIドキュメントをみればすぐわかると思うので、あえてここでは何も書きません。
本サンプルのソースはこちら
import java.awt.Color

import scala.swing._
import scala.swing.event._

/**
 * メインオブジェクト
 */
object Gardener extends SimpleSwingApplication {
  
  def top = new MainFrame {
    title = "AbstractFactory Sample"
    
    var garden:Garden   = new NoGarden
    val gardenPlot      = new GardenPanel{background = Color.WHITE};
    val buttonList      = List("菜園", "一年草園", "多年草園");
    
    // ButtonGroupの生成と設定
    val group           = new ButtonGroup
    buttonList.foreach(name => {
      val button = new RadioButton(name)
      group.buttons += button
      listenTo(button)
    });

    contents = new GridPanel(1,2) {

      // 左パネルの生成
      contents += new GridBagPanel {
        val constraints = pair2Constraints(0, 0)
        constraints.anchor = GridBagPanel.Anchor.FirstLineStart
        
        layout += new Label("庭園の種類") -> constraints;
        
        // ラジオボタンを配置する
        for (i <- 0 to (group.buttons.toList.length - 1)) {
          val constraints = pair2Constraints(0, (i + 1));
          constraints.anchor = GridBagPanel.Anchor.FirstLineStart
          constraints.ipady = 70
          layout += group.buttons.toList(i) -> constraints
        }
      }
      
      // 右パネルの生成
      contents += new GridBagPanel {
        val constraints = pair2Constraints(0, 0);
        constraints.ipady = 210
        constraints.ipadx = 210
        constraints.gridwidth = 3
        constraints.fill = GridBagPanel.Fill.Horizontal
        constraints.insets = new Insets(10, 5, 0, 5)
        layout += gardenPlot -> constraints
        
        // ボタンの生成と設定
        val list = List("Center", "Border", "Shade")
        for (i <- 0 to (list.length - 1)) {
          setButton(list(i), i, 1)
        }
        setButton("Quit", 1, 2)
        
        /**
         * ボタンを生成し、パネル上に配置する
         *
         * @param String buttoName
         * @param Int x
         * @param Int y
         * @return Unit
         */
        private def setButton(buttonName:String, x:Int, y:Int) = {
          val button = new Button(buttonName)
          val const = pair2Constraints(x, y);
          const.insets = new Insets(10, 5, 0, 5)
          layout += button -> const
          listenTo(button)
        }
        
        // ボタンのアクションを設定する
        reactions += {
          case ButtonClicked(b) => b.text match {
            case "Center" => {
              gardenPlot.centerPlant = garden.center.name
              gardenPlot.repaint
            }
            case "Border" => {
              gardenPlot.borderPlant = garden.border.name
              gardenPlot.repaint
            }
            case "Shade" => {
              gardenPlot.shadePlant = garden.shade.name
              gardenPlot.repaint
            }
            case "Quit" => System.exit(0)
          }
        }
      }
    }
    
    // ラジオボタンのアクションを設定する
    reactions += {
      case ButtonClicked(b) => b.text match {
        case "菜園" => {
          garden = new VegieGarden
          gardenPlot.clearPlants
        }
        case "一年草園" => {
          garden = new AnnualGarden
          gardenPlot.clearPlants
        }
        case "多年草園" => {
          garden = new PerennialGarden
          gardenPlot.clearPlants
        }
      }
    }
  }
}

/**
 * 植物を表すクラス
 */
case class Plant(name:String)

/**
 * 庭トレイと
 */
trait Garden {
  def shade:Plant
  def center:Plant
  def border:Plant
}

/**
 * デフォルトの庭クラス
 */
class NoGarden extends Garden {
  def shade = new Plant("")
  def center = new Plant("")
  def border = new Plant("")
}

/**
 * 一年草園の庭クラス
 */
class AnnualGarden extends Garden {
  def border = Plant("アブラナ")
  def center = Plant("キンセンカ")
  def shade  = Plant("コレウス")
}

/**
 * 多年草園の庭クラス
 */
class PerennialGarden extends Garden {
  def border = Plant("ベンケイソウ")
  def center = Plant("ケマンソウ")
  def shade  = Plant("チダケサシ")
}

/**
 * 菜園の庭クラス
 */
class VegieGarden extends Garden {
  def border = Plant("エンドウ")
  def center = Plant("トウモロコシ")
  def shade  = Plant("ブロッコリ")
}

/**
 * 庭の様子を表示するパネル
 */
class GardenPanel extends Panel {
  
  var borderPlant = "";
  var centerPlant = "";
  var shadePlant  = "";
  var garden      = new NoGarden
  
  def clearPlants = {
    borderPlant = ""
    centerPlant = ""
    shadePlant  = ""
    repaint()
  }
  
  override def paint(g:Graphics2D) = {
    super.paint(g)
    g.setColor(Color.LIGHT_GRAY)
    g.fillArc(1, 1, 80, 80, 0, 360)
    g.setColor(Color.BLACK)
    g.drawRect(0, 0, size.width - 1, size.height - 1)
    g.drawString(centerPlant, 100, 50)
    g.drawString(borderPlant, 75, 120)
    g.drawString(shadePlant, 10, 40)
  }
}

0 件のコメント:

コメントを投稿