2017年度資料
2018年度差し替え予定
以下、旧資料
Javaとデザインパターンに関する近年の動向:
GoF の古典的な23個のデザインパターンに対して、 Java8 以降のコードで重要性が低くなるパターンについて考察されている。
※ interface のデフォルト実装と ラムダ(lambda)記法の導入が大きく影響している。
※※ Java 8 新機能つまみぐい Java8のラムダ記法についてはこちらの記事に簡潔な解説がある。
OOPのデザインパターンと関数型のデザインパターンを比較しながら、関数型プログラミング言語のコード作成について解説されている。JavaVMで動く関数型言語 Scala や同じくJVMで動くスクリプト言語Groovyを用い、解説している。
ラムダ式でCommandパターンで書かれたコードをシンプルにする ~ ラムダ式使いへの第一歩
その他:
ゲームプログラミングとデザインパターン http://marupeke296.com/DP_main.html
2017演習
プログラミング教育環境 Scratch の様に、キャラクターに画像・サウンドを登録して、画面上で複数のキャラクターのアニメーションを制御するアプリを作成する。
アプリ作成の過程で遭遇するデザインパターン:
インタラクティブに画面上のキャラクターの操作を行うUIを作成 Compositeパターン、Observerパターン
オーディオクリップとイメージオブジェクトの生成 Factory Methodパターン
キャラクターに階層的に別のキャラクターを追加してグループとして扱う Composite パターン
キャラクターの操作と操作のUndo機能 Commandパターン
複数のキャラクターの処理 Iteratorパターン
画面の状況のスナップショットを記録 Memento パターン(Save と Load 機能)
準備:
プロジェクト名 java4 を作成する。
このページに記載したコードを L11a クラスを作成して、コピペする。
※パッケージは指定しない java4 と入力済みの箇所を削除して デフォルトパッケージ にする。
JavaによるJFrame(ウィンドウ)アプリケーションの作成:
参照サイト: https://www.javadrive.jp/tutorial/jframe/index6.html
基本形:
import javax.swing.JFrame;
class L11a {
public static void main(String args[]) {
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setVisible(true); // ウインドウを表示状態にする
}
}
ボタンを追加する&ウインドウを閉じるボタンでアプリを終了する様に設定する。
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
class L11a {
public static void main(String args[]) {
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JButton btn = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
add(btn, BorderLayout.NORTH); // オブジェクトをウインドウの上部に追加
setVisible(true); // ウインドウを表示状態にする
}
}
ボタンをウインドウの上端に複数配置する。
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class L11a {
public static void main(String args[]) {
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JPanel bp = new JPanel(); // コンポーネント追加用パネル(コンテナ)
JButton btn1 = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
bp.add(btn1); // オブジェクトをパネルの上部に追加
JButton btn2 = new JButton("ボタン2"); // ボタンオブジェクトの作成と追加
bp.add(btn2); // オブジェクトをパネルの上部に追加
JButton btn3 = new JButton("ボタン3"); // ボタンオブジェクトの作成と追加
bp.add(btn3); // オブジェクトをパネルの上部に追加
add(bp,BorderLayout.NORTH); // パネルをウインドウの上端に設置
setVisible(true); // ウインドウを表示状態にする
}
}
※このコードでは、Compositeパターンが確認できる。JFrameは、GUIコンポーネントを格納するコンテナであり、コンテナ内に再帰的に別のコンポーネントを配置するためのJPanelコンテナを配置して階層構造でGUIコンポーネントを管理している。
ボタンを押すと、サウンドが再生されるようにアクションを設定する。
再生用のサウンドファイルは、適当に用意して(プロジェクト java3からコピーしてもOK)、L11aクラスと同じ階層に配置する。
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class L11a {
static AudioClip ac1, ac2, ac3;
public static void main(String args[]) {
ac1 = Applet.newAudioClip(L11a.class.getResource("gun02.wav")); //音声ファイル読み込み
ac2 = Applet.newAudioClip(L11a.class.getResource("nc52380.wav")); //音声ファイル読み込み
ac3 = Applet.newAudioClip(L11a.class.getResource("se_ymc01.wav")); //音声ファイル読み込み
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JPanel bp = new JPanel(); // コンポーネント追加用パネル(コンテナ)
JButton btn1 = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
bp.add(btn1); // オブジェクトをパネルの上部に追加
JButton btn2 = new JButton("ボタン2"); // ボタンオブジェクトの作成と追加
bp.add(btn2); // オブジェクトをパネルの上部に追加
JButton btn3 = new JButton("ボタン3"); // ボタンオブジェクトの作成と追加
bp.add(btn3); // オブジェクトをパネルの上部に追加
btn1.addActionListener(event -> { L11a.ac1.play(); }); //音声ファイル再生
btn2.addActionListener(event -> { L11a.ac2.play(); });
btn3.addActionListener(event -> { L11a.ac3.play(); });
add(bp,BorderLayout.NORTH); // パネルをウインドウの上端に設置
setVisible(true); // ウインドウを表示状態にする
}
}
※L11a.class のコードは、 L11a のclass 定義には class フィールドがないので、一見バグに見える。
Javaの全クラスの祖先となる Object クラスに class フィールドが存在する。
Javaの全クラスは自動的に Objectクラスのサブクラスとなる。
※このコードでは、Factory Methodパターンが確認できる。オーディオクリップ用のオブジェクトの生成の部分。newAudioClip メソッドでオブジェクト生成
※このコードでは、Observerパターンが確認できる。ボタンを押したときのアクションのコードを、イベントの発生を監視するオブジェクトに登録する。
参照サイト: https://www.javadrive.jp/tutorial/jframe/index6.html では、古いスタイルのコードが例示されている。
他のページに暗黙のクラス(無名クラス)を利用した、新しいスタイルの解説がある。
旧方式に比べ、オブザーバー用のオブジェクトをclass定義(ActionPerformedインターフェースを実装した)する必要がなくなった。
参照:https://www.javadrive.jp/tutorial/event/index1.html
上記のコードのイベント処理はさらに新しい Java8 で導入された関数インターフェース(ラムダ式)を利用して書かれている。
入力が イベントオブジェクト1つ 処理結果は void の対応関係から推測可能な、イベントの型やイベント処理のメソッド名を省略できる。
event -> { L11a.ac1.play(); }
の部分で、 event が関数インターフェースの引数(型の指定は省略)で、{ } の中がイベント処理用のコード。
{ } の内部には for や if で利用するコードのブロック同様に、コードを複数記述可能。
for や if のコードブロックと異なり、関数インターフェースのブロックは、遅延評価(必要に応じて実行。場合によっては繰り返し)される。
参照:https://qiita.com/oohira/items/9c13f92815266cc5112c
JFrameの中央部分にJPanelを配置する。
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class L11a {
static AudioClip ac1, ac2, ac3;
public static void main(String args[]) {
ac1 = Applet.newAudioClip(L11a.class.getResource("gun02.wav")); // 音声ファイル読み込み
ac2 = Applet.newAudioClip(L11a.class.getResource("nc52380.wav")); // 音声ファイル読み込み
ac3 = Applet.newAudioClip(L11a.class.getResource("se_ymc01.wav")); // 音声ファイル読み込み
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JPanel bp = new JPanel(); // コンポーネント追加用パネル(コンテナ)
JButton btn1 = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
bp.add(btn1); // オブジェクトをパネルの上部に追加
JButton btn2 = new JButton("ボタン2"); // ボタンオブジェクトの作成と追加
bp.add(btn2); // オブジェクトをパネルの上部に追加
JButton btn3 = new JButton("ボタン3"); // ボタンオブジェクトの作成と追加
bp.add(btn3); // オブジェクトをパネルの上部に追加
btn1.addActionListener(event -> {
L11a.ac1.play();
}); // 音声ファイル再生
btn2.addActionListener(event -> {
L11a.ac2.play();
});
btn3.addActionListener(event -> {
L11a.ac3.play();
});
add(bp, BorderLayout.NORTH); // パネルをウインドウの上端に設置
add(new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(new Color(255,0,0));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
});
setVisible(true); // ウインドウを表示状態にする
}
}
※ここで配置するJPanel は暗黙のクラス定義を利用して、paintComponet メソッド(コンポーネント自身の描画方法。ここでは背景色を描画)をオーバーライドしてカスタマイズしている。
※Graphics クラスについて: 参照 https://codezine.jp/article/detail/2543
グラフィックスコンテキストは、描画の指示(コマンド) と 描画に必要な詳細情報 を分離して扱えるようにしたもの。
直線を描く(コマンド) と 線の太さやスタイル色 を決めるコマンドを独立して提供できる。デフォルトの線やスタイルを提供できる。
描画対象(イメージデータ、パネル)からグラフィクスコンテキストを取得して利用する。描画対象に描画するためのツールがグラフィクスコンテキスト。
画像を表示するコードを追加する。 参照: http://maedakobe.rw.xsi.jp/java2/image.htm
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class L11a {
static AudioClip ac1, ac2, ac3;
static Image img1, img2;
public static void main(String args[]) {
ac1 = Applet.newAudioClip(L11a.class.getResource("gun02.wav")); // 音声ファイル読み込み
ac2 = Applet.newAudioClip(L11a.class.getResource("nc52380.wav")); // 音声ファイル読み込み
ac3 = Applet.newAudioClip(L11a.class.getResource("se_ymc01.wav")); // 音声ファイル読み込み
try {
img1 = ImageIO.read(L11a.class.getResource("kohashi.jpg")); // 画像ファイル読み込み
img2 = ImageIO.read(L11a.class.getResource("photo1-2.jpg")); // 画像ファイル読み込み
} catch (IOException e) {
e.printStackTrace();
}
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JPanel bp = new JPanel(); // コンポーネント追加用パネル(コンテナ)
JButton btn1 = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
bp.add(btn1); // オブジェクトをパネルの上部に追加
JButton btn2 = new JButton("ボタン2"); // ボタンオブジェクトの作成と追加
bp.add(btn2); // オブジェクトをパネルの上部に追加
JButton btn3 = new JButton("ボタン3"); // ボタンオブジェクトの作成と追加
bp.add(btn3); // オブジェクトをパネルの上部に追加
btn1.addActionListener(event -> {
L11a.ac1.play();
}); // 音声ファイル再生
btn2.addActionListener(event -> {
L11a.ac2.play();
});
btn3.addActionListener(event -> {
L11a.ac3.play();
});
add(bp, BorderLayout.NORTH); // パネルをウインドウの上端に設置
add(new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(new Color(255,0,0));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.drawImage(L11a.img1, 0, 0, this);
g2.drawImage(L11a.img2, 300, 300, 200, 100, this);
g2.dispose();
}
});
setVisible(true); // ウインドウを表示状態にする
}
}
※ここで用いた画像描画メソッド drawImage は、画像のオリジナルのサイズで指定したxy座標に配置する方法と、画像の幅と高さを指定して配置する方法の2通りで示した。
画像を切り替えるボタンの作成。
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class L11a {
static AudioClip ac1, ac2, ac3;
static Image img1, img2;
public static void main(String args[]) {
ac1 = Applet.newAudioClip(L11a.class.getResource("gun02.wav")); // 音声ファイル読み込み
ac2 = Applet.newAudioClip(L11a.class.getResource("nc52380.wav")); // 音声ファイル読み込み
ac3 = Applet.newAudioClip(L11a.class.getResource("se_ymc01.wav")); // 音声ファイル読み込み
try {
img1 = ImageIO.read(L11a.class.getResource("kohashi.jpg")); // 画像ファイル読み込み
img2 = ImageIO.read(L11a.class.getResource("photo1-2.jpg")); // 画像ファイル読み込み
} catch (IOException e) {
e.printStackTrace();
}
MyFrame myframe = new MyFrame();
}
}
class MyFrame extends JFrame {
Image img;
DrawPanel drawpanel;
MyFrame() {
setTitle("課題11"); // ウインドウのタイトル設定
setSize(800, 600); // ウインドウのサイズ
setLocationRelativeTo(null); // パソコンの画面中央にウインドウを配置
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ウインドウの閉じるボタンでアプリ終了の設定
JPanel bp = new JPanel(); // コンポーネント追加用パネル(コンテナ)
JButton btn1 = new JButton("ボタン1"); // ボタンオブジェクトの作成と追加
bp.add(btn1); // オブジェクトをパネルの上部に追加
JButton btn2 = new JButton("ボタン2"); // ボタンオブジェクトの作成と追加
bp.add(btn2); // オブジェクトをパネルの上部に追加
JButton btn3 = new JButton("ボタン3"); // ボタンオブジェクトの作成と追加
bp.add(btn3); // オブジェクトをパネルの上部に追加
btn1.addActionListener(event -> {
L11a.ac1.play();
}); // 音声ファイル再生
btn2.addActionListener(event -> {
L11a.ac2.play();
});
btn3.addActionListener(event -> {
L11a.ac3.play();
});
bp.add(new JButton("ボタン4") {
{
addActionListener(event -> {
img = L11a.img1;
drawpanel.repaint();
});
}
});
bp.add(new JButton("ボタン5") {
{
addActionListener(event -> {
img = L11a.img2;
drawpanel.repaint();
});
}
});
add(bp, BorderLayout.NORTH); // パネルをウインドウの上端に設置
drawpanel = new DrawPanel();
add(drawpanel);
setVisible(true); // ウインドウを表示状態にする
}
class DrawPanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(new Color(255, 0, 0));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.drawImage(img, 0, 0, this);
g2.dispose();
}
}
}
※ボタン4とボタン5のコードは、JButtonオブジェクトを変数に格納せずに直接生成して addメソッドの引数にしている。
さらに、初期化ブロックを使用し、ボタンのインスタンス生成時にイベントリスナーを登録している。
ボタンを押すと img の画像オブジェクトが切り替わる。再描画をパネルに対して指示して、画面を更新する。
※ボタンとパネルを連動させるために、パネルに名前が必要となる。
暗黙のクラスで生成していたJPanelを、L11aクラスの内部専用の インナークラス DrawPanel として定義し、インスタンス化したものをフレームに追加するようにコードを変更した。
今回はここまで。続きは次回。
今回実装できた項目:
インタラクティブに画面上のキャラクターの操作を行うUIを作成 Compositeパターン、Observerパターン
オーディオクリップとイメージオブジェクトの生成 Factory Methodパターン
次回実装する項目:
キャラクターに階層的に別のキャラクターを追加してグループとして扱う Composite パターン
キャラクターの操作と操作のUndo機能 Commandパターン
複数のキャラクターの処理 Iteratorパターン
画面の状況のスナップショットを記録 Memento パターン(Save と Load 機能)
以下は、2016年度の内容
2017年度はパス。
準備:
プロジェクトを作成する。
プロジェクト名 Project6
Project6の src フォルダに Project5 の src フォルダから L8_1.java や wav ファイルなど必要なものをコピーする。
src パッケージがない というエラーが出たら、 L8_1.java や関連ファイルを デフォルトパッケージに移動する。
class の整理:
L8_1 から不要なコードを削除する。 main メソッド や System.out.println()でのメッセージ出力など。
コマンドの実行タイミングの変更:
L8_1 の paintメソッドで history.execute() している部分を、マルチスレッドにより画面描画とは別のスレッドで実行されるように L8_1 を修正。
public class L8_1 extends Applet implements Runnable{
run メソッドを追加(コード修正機能)。ここでコマンドを実行するように、paintからコードを移動。
@Override
public void run() {
history.execute();
}
start メソッドを追加。(右クリック→ソース→メソッドのオーバーライド)
public void start() {
super.start();
(new Thread(this)).start();
}
Cat クラスを修正する:
class Cat extends Animal implements ItemOwner {
String item;
int x;
int y;
Image img;
Dir dir;
enum Dir {UP, DOWN, RIGHT, LEFT};
Cat のメソッド修正:
Cat(String name,Image img) {
super(name, Size.Small);
this.img = img;
dir = Dir.UP;
}
void walk(int step) {
// ねこが step 歩く
System.out.println(getName() + "が" + step + "歩移動" + dir.toString() + "方向に。");
switch(dir) {
case UP:
y -= step;
break;
case DOWN:
y += step;
break;
case RIGHT:
x += step;
break;
case LEFT:
x -= step;
}
}
コマンドのタイプを追加する:
class WalkCommand implements Command {
Cat cat;
public WalkCommand(Cat cat) {
this.cat = cat;
}
public void execute() {
cat.walk(10);
repaint();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class UpCommand implements Command {
Cat cat;
public UpCommand(Cat cat) {
this.cat = cat;
}
public void execute() {
cat.dir =Cat.Dir.UP;
}
}
その他のコマンドの作成は、受講生に任せる。
画面内に、キャラクターが描画されるようにする。
キャラクターの登録場所の準備:
private Stack<Cat> catstack = new Stack<Cat>();
キャラクターの登録例:
Cat mycat = new Cat("引っ越し",img);
catstack.push(mycat);
Cat mycat2 = new Cat("引っ越し2",img);
catstack.push(mycat2);
※ Catは catstack につまれている場合に画面に描画されるものとする。
キャラクター画像の読み込み例:
Image img;
img = getImage(getCodeBase(), "kohashi.jpg");
ペイントメソッドに、catstacckの猫を描画するコードを追加する。
public void paint(Graphics g) {
offg.setColor(Color.white);
offg.fillRect(0, 0, 600, 600); // 画面を消去する
for(Cat cat:catstack) {
offg.drawImage(cat.img, cat.x, cat.y, 50, 100, this);
}
g.drawImage(offimg, 0, 0, this); // オフスクリーンを描画
}
init メソッド内で新しく定義したコマンドを使用して、Cat に行動させる。
例)
右斜め下に、階段状に移動し、最後にアイテムを使用する。
Command cmd1 = new UseItemCommand(mycat);
Command cmdR = new RightCommand(mycat);
Command cmdW = new WalkCommand(mycat);
Command cmdU = new UpCommand(mycat);
Command cmdD = new DownCommand(mycat);
for(int i=0;i<10;i++) {
history.append(cmdR);
history.append(cmdW);
history.append(cmdD);
history.append(cmdW);
}
history.append(cmd1);
課題:
画面内に猫を複数登録し、それぞれ別々の行動を取らせる。
応用:
・Catが所持している Item も画面上に描画されるようにする。
Itemに Image のフィールドを作成する。
・Composite パターンを使用して、 Catに所属する Item として Cat 自身も所属できるようにする。
・Vistor パターンを使用して、画面に登録された全キャラクターを操作するコマンドを作成する。
指定した点に集合
キャラクターを指定して画面から削除
など。