読者です 読者をやめる 読者になる 読者になる

MVCにおけるViewの表示方法 トランザクションスクリプト、ドメインモデル

MVCモデルについて - GeekなNooblog
プログラマーが意識するべきUI設計指針 3つのMVCモデル - GeekなNooblog
MVCモデルの問題点を解決するPMモデルとMVPモデル - GeekなNooblog
MVCにおけるViewの表示方法 トランザクションスクリプト、ドメインモデル - GeekなNooblog

# 2011/9/4追加
最近気がついたのですがドメインモデルは名前の通りモデルに適用するものなので、ModelとViewの対応についてトランザクションスクリプトとドメインモデルを論じるのは見当違いだったかもしれません。

トランザクションスクリプト、ドメインモデルの違い

  • 詳細は最下部にある参考、あとはぐぐってみてください。
  • 実装の違いもそうですが、呼び出し元のメソッドのわかりやすさが大きな違いとなります。
  • 早速実際のコード例を挙げていきたいと思います。人間が走ると体重が減るというモデルを表しています。
トランザクションスクリプト
  • オブジェクトを構造体的に使って、オブジェクトに振る舞いを持たせません。それとは別に振る舞い専用のクラスを用意します。
  • 振る舞い専用のクラスは、そのオブジェクトからget -> 処理 -> setという感じに処理をします。
  • オブジェクト指向というか手続き型みたいな感じです。
  • 以下のコードはエラー処理を省いています。
  • Mainクラス
public class Main {
	public static void main(String[] args) {
		Human human = new Human(60);
		HumanRunning humanRunning = new HumanRunning(human);
		humanRunning.exec(10);
		humanRunning.exec(5);
		humanRunning.exec(4);
		
	}
}
  • Humanクラス
public class Human {
	private int weight;
	public Human(int weight) {
		this.weight = weight;
		printWieght();
	}
	public int getWeight() {
		return weight;
	}
	public void setWeight(int weight) {
		this.weight = weight;
		printWieght();
	}
	private void printWieght() {
		System.out.println("weight = " + weight);
	}
}
  • HumanRunningクラス
public class HumanRunning {
	private Human human;

	public HumanRunning(Human human) {
		this.human = human;
	}

	public void exec(int distance) {
		human.setWeight(human.getWeight() - distance / 5);
	}
}
実行結果
  • 以降実行結果は同じになるため省略

weight = 60
weight = 58
weight = 57
weight = 57

トランザクションスクリプトというか手続き型
  • トランザクションスクリプトの手続き型度合いにも程度があると考えています。
  • 以下最悪なパターン
  • 呼び出し元のメソッドに処理ががっちりとい書いてあります。
  • パッと見てmainメソッドで何をやっているのかわかりません。
public class Human {
	private int weight;

	public Human(int weight) {
		this.weight = weight;
		printWieght();
	}

	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		this.weight = weight;
		printWieght();
	}

	private void printWieght() {
		System.out.println("weight = " + weight);
	}

	public static void main(String[] args) {
		Human human = new Human(60);
		int weight = human.getWeight();
		weight -= 10 / 5;
		human.setWeight(weight);
		human.setWeight(human.getWeight()-5/5);
		human.setWeight(human.getWeight()-4/5);
	}
}
ドメインモデル
  • オブジェクト自体が振る舞いを持ちます。
  • the オブジェクト指向という感じ美しいですね。
  • カプセル化されています。
    • mainはrunという処理が実際どう処理しているかを知る必要がありません。
    • mainはHumanクラスのフィールドを知る必要がありません。
public class Human {
	private int weight;

	public Human(int weight) {
		this.weight = weight;
		printWieght();
	}

	public void run(int distance) {
		weight -= distance / 5;
		printWieght();
	}

	private void printWieght() {
		System.out.println("weight = " + weight);
	}

	public static void main(String[] args) {
		Human human = new Human(60);
		human.run(10);
		human.run(5);
		human.run(4);
	}
}

MVCにおけるViewの表示方法

ここからが本題です。
僕は当然MVCにおいてもドメインモデルの方がいいのだろうなっと思っていたのですが、MVCでドメインモデルを実現してしまうとViewに関する部分で問題が発生します。



トランザクションスクリプト
  • 特に違和感はないと思います。
  • ViewはModelから値を取得して表示しています。
  • Mainクラス
public class Main {
	public static void main(String[] args) {
		Model model = new Model("GeekなCamera", 100, 10);
		View view = new View(model);
		Controller controller = new Controller(model);
		view.addToButtonActionListener((ActionListener) controller);
		model.addObserver(view);
	}
}
  • Controllerクラス
public class Controller implements ActionListener {
	private Model model;

	public Controller(Model model) {
		this.model = model;
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		model.sell();
	}
}
  • Modelクラス
public class Model extends Observable {
	private String name;
	private int price;
	private int stock;
	public Model(String name, int price, int stock) {
		this.name = name;
		this.price = price;
		this.stock = stock;
	}
	public void sell() {
		if (stock > 0) {
			stock--;
			// 以下の2行でModelに変更があったことをObserverに通知する
			setChanged();
			notifyObservers();
		}
	}
	public String getName() {
		return name;
	}
	public int getPrice() {
		return price;
	}
	public int getStock() {
		return stock;
	}
}
  • Viewクラス
public class View extends JFrame implements Observer {
	private JButton button;
	private JLabel label;
	private Model model;

	/**
	 * View Sampleクラスのコンストラクタ Viewの設定を行う。
	 */
	public View(Model model) {
		this.model = model;
		// ウィンドウが閉じられたときプログラムを終了するように設定します。
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// コンポーネントの配置のレイアウトを指定します。
		this.setLayout(new FlowLayout());
		// JFrameに追加するラベルを定義します。
		label = new JLabel();
		this.setItemInformation();
		// JFrameに作成したラベルを追加します。
		this.add(label);
		// JFrameに追加するButtonの定義
		button = new JButton("購入");
		// ボタンが押されたとき呼び出されるActionListenerに追加します。
		// JFrameに作成したボタンを貼り付けます。
		this.add(button);
		// JFrameのサイズを追加されたコンポーネント(ボタンやラベル)に合わせて最適化を行います。
		this.pack();
		// 作成したJFrameの表示をします。
		this.setVisible(true);
	}

	public void addToButtonActionListener(ActionListener actionListener) {
		button.addActionListener(actionListener);
	}

	/**
	 * Observerインターフェースのupdateメソッド
	 * ModelでnotifyObservers()などが実行されるとupdateメソッドが実行される。
	 * 
	 * @param o
	 *            変更されたモデルのクラス 今回はModelクラス
	 * @param arg
	 *            notifyObservers実行時に引数を与えたものをargとして受け取ることができる。今回はなし
	 *            numberを与えてもよし
	 */
	@Override
	public void update(Observable o, Object arg) {
		this.setItemInformation();
	}

	private void setItemInformation() {
		// 変更されたmodelの情報を取得します。
		String name = model.getName() + " ";
		String price = "価格:$" + Integer.toString(model.getPrice()) + " ";
		String stock = "在庫数:" + Integer.toString(model.getStock());
		// 数値をラベルにセットします。
		label.setText(name + price + stock);
	}
}
ドメインモデル
  • Viewは値を取得して表示するのではなく、view自信をModelに渡して処理を委譲しています。
  • しかしModelからViewに依存してしまっていて、大変気持ち悪いです。というかもうMVCですらないとも言えます。

MainとControllerは上記とまったく同じなので省略

  • Modelクラス
public class Model extends Observable {
	private String name;
	private int price;
	private int stock;

	public Model(String name, int price, int stock) {
		this.name = name;
		this.price = price;
		this.stock = stock;
	}

	public void sell() {
		if (stock > 0) {
			stock--;
			// 以下の2行でModelに変更があったことをObserverに通知する
			setChanged();
			notifyObservers();
		}
	}

	public String getName() {
		return name;
	}

	public int getPrice() {
		return price;
	}

	public int getStock() {
		return stock;
	}

	public void setInformation(View view) {
		view.viewInformation(name, Integer.toString(price),Integer.toString(stock));
	}
}
  • Viewクラス
public class View extends JFrame implements Observer {
	private JButton button;
	private JLabel label;
	private Model model;

	/**
	 * View Sampleクラスのコンストラクタ Viewの設定を行う。
	 */
	public View(Model model) {
		this.model = model;
		// ウィンドウが閉じられたときプログラムを終了するように設定します。
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// コンポーネントの配置のレイアウトを指定します。
		this.setLayout(new FlowLayout());
		// JFrameに追加するラベルを定義します。
		label = new JLabel();
		model.setInformation(this);
		// JFrameに作成したラベルを追加します。
		this.add(label);
		// JFrameに追加するButtonの定義
		button = new JButton("購入");
		// ボタンが押されたとき呼び出されるActionListenerに追加します。
		// JFrameに作成したボタンを貼り付けます。
		this.add(button);
		// JFrameのサイズを追加されたコンポーネント(ボタンやラベル)に合わせて最適化を行います。
		this.pack();
		// 作成したJFrameの表示をします。
		this.setVisible(true);
	}

	public void addToButtonActionListener(ActionListener actionListener) {
		button.addActionListener(actionListener);
	}

	/**
	 * Observerインターフェースのupdateメソッド
	 * ModelでnotifyObservers()などが実行されるとupdateメソッドが実行される。
	 * 
	 * @param o
	 *            変更されたモデルのクラス 今回はModelクラス
	 * @param arg
	 *            notifyObservers実行時に引数を与えたものをargとして受け取ることができる。今回はなし
	 *            numberを与えてもよし
	 */
	@Override
	public void update(Observable o, Object arg) {
		model.setInformation(this);
	}

	public void viewInformation(String name, String price, String stock) {
		// 変更されたmodelの情報を取得します。
		name = name + " ";
		price = "価格:$" + price + " ";
		stock = "在庫数:" + stock;
		// 数値をラベルにセットします。
		label.setText(name + price + stock);
	}
}

何もまとまってないまとめ

基本はドメインモデルを意識した方がいいと思うんですけど、すべてがすべてその方がいいという訳ではないんですかね
適材適所!


# PS
ファウラーさんに続いてMatzさんの記事に遭遇することも多くなってきて納得してばかりです。
Ruby触ってみようかな

参考

比較

まつもと直伝 プログラミングのオキテ第2回 (2) - まつもと直伝 プログラミングのオキテ:ITpro
http://itpro.nikkeibp.co.jp/article/COLUMN/20050913/221049/

備忘録: 2008年12月
http://owen-tudor.cocolog-nifty.com/blog/2008/12/index.html

Martin Fowler's Bliki in Japanese - ドメインロジックとSQL
http://capsctrl.que.jp/kdmsnr/wiki/bliki/?DomainLogicAndSQL

System.Exit – トランザクションスクリプトとドメインモデルについてのあれこれ
http://jugyo.org/blog/1344

ドメインモデルに対する日米の温度差|Ouobpo
http://ameblo.jp/ouobpo/entry-10036477015.html

2007-11-01 - S_a_k_Uの日記みたいなDB 〜サクゥーと呼ばないで〜
http://d.hatena.ne.jp/S_a_k_U/20071101#p1

トランザクションスクリプト派?

賢いデータは必要なのか (arclamp.jp アークランプ)
http://www.arclamp.jp/blog/archives/000541.html

賢いデータは必要なのか その2 (arclamp.jp アークランプ)
http://www.arclamp.jp/blog/archives/000552.html

ドメインモデル派?

ドメインモデル貧血症 - Strategic Choice
http://d.hatena.ne.jp/asakichy/20110203/1296698836

ドメインロジックの実装方法とドメイン駆動設計
http://www.slideshare.net/ouobpo/ss-326835

エリック・エヴァンスのドメイン駆動設計

広告を非表示にする