// S-JISです
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
/**
* オブジェクト指向的なプログラミング例.
* commandパターンの例です.
* @author Shin
* @version 1.0
*/
public class CommandTest extends Frame {
// メニューを処理するクラスを生成します
private CommandManager commandManager = new CommandManager();
// メニューに応じた「要求」群
private Command[] menuItem;
public CommandTest() {
MyCanvas panel = new MyCanvas();
// 要求の配列の生成(要素数 = 6:0〜5)
menuItem = new Command[] {
new RandomPrintCommand(panel, "Random"),
new RandomPrintCommand(panel, "★Random★"),
new SequencePrintCommand(panel, "Sequence"),
new SequencePrintCommand(panel, "★Sequence★"),
new UndoCommand(commandManager),
new ExitCommand(this)
};
// menuItem[0] = new RandomPrintCommand();
// menuItem[1] = new RandomPrintCommand();
// menuItem[2] = new SequencePrintCommand();
// menuItem[3] = new SequencePrintCommand();
// menuItem[4] = new UndoCommand();
// menuItem[5] = new ExitCommand();
// と等価です。
// ↑代入しているオブジェクトはみんなCommand型を持ちます。
// (実装しています)
// 抽象データ型の威力はこういうところにあります。
// メニューを生成します。
setMenuBar(createMenuBar());
add(panel);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent ev) {
exit();
}
});
}
private MenuBar createMenuBar() {
MenuBar bar = new MenuBar();
Menu menu = new Menu("menu");
MenuItem item;
for (int i = 0; i < menuItem.length; i++) {
// ActionListener 内から参照可能にするため、final 変数に格納します。
final Command command = menuItem[i];
// あまりスマートな作りではないですが、MenuItem と Command
// の関連付けを行っています。
item = new MenuItem(command.getCommandName());
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
commandManager.execute(command);
}
});
menu.add(item);
}
bar.add(menu);
return bar;
}
public void exit() {
dispose();
System.exit(0);
}
/**
* このメソッドから実行を開始します.
* @param args コマンドラインパラメタ
* このプログラムでは使用しません(無視です).
*/
public static void main(String[] args) {
CommandTest frame = new CommandTest();
frame.pack();
frame.setVisible(true);
}
}
/****************************************************************************/
/**
* メニューとコマンドの実行機能を定義.
*
* このクラスは実行(execute())したコマンドの履歴を保持し、
* Undoを実現します.
* 実行するコマンドを実装したコマンドオブジェクト
* を受け取るところがみそです.
* @author Shin
* @version 1.0
*/
class CommandManager {
// 実行したコマンドの履歴(Undoに使えます)
private Command[] history = new Command[20];
private int index = 0;
/**
* メニュー処理オブジェクトを生成します.
*
* 何もしないコンストラクタ(定義自体を省略してもかまいません) */ public CommandManager() { } /** * メニューのコマンドを実行します. *
* 何を行うかはCommandクラスで定義します.
* Undoの為に実行したコマンドをヒストリに記録していきます.
* @param command コマンドオブジェクト
* @return true:終了コマンド
*/
public void execute(Command command) {
try {
if (command.execute()) {
// Undo可能なコマンドであれば履歴に追加。
// ヒストリにはコピーを格納していく事になります。
history[index] = (Command)command.clone();
index = (index + 1) % 20;// Max20回分のコマンドを記録します
}
} catch (Throwable e) {
// コマンドの失敗など
}
}
/**
* Undoを行います.
*
* 直前のコマンドに対して「復元」を依頼します. */ public void undo() throws Throwable { index = (index + 20 - 1) % 20; if (history[index] == null) { return; } history[index].undo(); } } /****************************************************************************/ /** * コマンドの機能を定義. *
* 基本はコマンドの動作メソッドだけ定義すればいいんすけど
* 今回はメニューとしての機能を定義してるなー.
* ほんとにコマンドパターンなのかちょっと心配です.
* @author Shin
* @version 1.0
*/
//interfaceを定義する事で、それを実装するクラスにinterfaceの「型」
//を持たせる事ができます(重要)
//ここではCommand型と共にCloneable型を実装クラスに持たせる事になります.
interface Command extends Cloneable {
// interfaceで定義するメソッドは全てpublic abstractです.
// 従ってpublic abstractは省略してもかまいません.
/**
* コマンド名を返します.
* @return コマンドの名前(メニューに表示すべきもの)
*/
String getCommandName();
/**
* コマンドを実行します.
* @return true:undo可能なコマンド
*/
boolean execute() throws Throwable;
/**
* 実行したコマンドについて実行前の状態に戻す.
* return true:復元できなかった
*/
void undo() throws Throwable;
/**
* オブジェクトの複製を返します.
*
* ObjectクラスのメソッドですがUndo用のコマンド実行履歴のために * 複製可能でなければなりません. * return Commandオブジェクトの複製 */ // これに関してはclone以外のメソッド名にしてCommandを返させるのも手です。 Object clone(); } /****************************************************************************/ // 注:以下は実装クラス群なので、メソッドのコメントは省略します。 /** * コマンドの実装その一. * @author Shin * @version 1.0 */ class RandomPrintCommand implements Command { private MyCanvas canvas; private String text; // 選択されるたびにランダムな位置に文字列を表示するコマンドです。 public RandomPrintCommand(MyCanvas canvas, String text) { this.canvas = canvas; this.text = text; } public String getCommandName() { return "ランダム表示[" + text + "]"; } public boolean execute() { canvas.addText(new Point( (int)(Math.random() * 350), (int)(Math.random() * 380) + 10), text); return true; } public void undo() { canvas.removeText(); } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } ////////////////////////////////////////////////////////////////////////////// /** * コマンドの実装その二. * @author Shin * @version 1.0 */ class SequencePrintCommand implements Command { private MyCanvas canvas; private String text; private int pos = 15; // 選択されるたびに文字列を表示するコマンドです。 public SequencePrintCommand(MyCanvas canvas, String text) { this.canvas = canvas; this.text = text; } public String getCommandName() { return "連続表示[" + text + "]"; } public boolean execute() { canvas.addText(new Point(pos, pos), text); pos = (pos + 30) % 390; return true; } public void undo() { canvas.removeText(); } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } ////////////////////////////////////////////////////////////////////////////// /** * コマンドの実装その三. * @author Shin * @version 1.0 */ class UndoCommand implements Command { private CommandManager commandManager; public UndoCommand(CommandManager commandManager) { this.commandManager = commandManager; } public String getCommandName(){ return "Undo"; } public boolean execute() throws Throwable { commandManager.undo(); // Undo不可能コマンドです。 return false; } // ダミーになります public void undo() { } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } } ////////////////////////////////////////////////////////////////////////////// /** * コマンドの実装その四. * @author Shin * @version 1.0 */ class ExitCommand implements Command { private CommandTest application; public ExitCommand(CommandTest application) { this.application = application; } public String getCommandName() { return "Exit"; } public boolean execute() { application.exit(); // この場合ここにはたどりつきませんが・・・。 return false; } // ダミーになります public void undo() { } // ダミーになります public Object clone() { return null; } } ////////////////////////////////////////////////////////////////////////////// /** * アプリケーションのクライアント領域のサンプルです. * 複数の文字列を表示することができます. * @author Shin * @version 1.0 */ class MyCanvas extends Canvas { private Vector objects = new Vector(); public Dimension getPreferredSize() { return new Dimension(400, 400); } /** * 表示すべきテキストを追加します. * @param p 表示位置 * @param text 表示する文字列 */ public void addText(Point p, String text) { objects.add(new Text(p, text)); repaint(); } /** (取り敢えず)末尾の要素を削除します. */ public void removeText() { if (objects.size() > 0) { objects.remove(objects.size() - 1); } repaint(); } public void paint(Graphics g) { // 同期の問題があるのでcloneかsynchronizedが通常は必要です。 Enumeration enum = ((Vector)objects.clone()).elements(); while (enum.hasMoreElements()) { ((Text)enum.nextElement()).draw(g); } } /** * コレクションに格納するデータクラスです. */ // 今回は面倒なのでここにdraw()も実装しました。 // さらなる抽象化はまた今度・・・。 class Text { private Point p; private String text; public Text(Point p, String text) { this.p = p; this.text = text; } public void draw(Graphics g) { g.drawString(text, p.x, p.y); } } }