ukiyuの思考研究所。

人生道半ばでの知識の棚卸し、次のお仕事決まるまで。オブジェクト指向モデリングとかに関してつらつらと。ご意見、ご感想等は気楽にどうぞ。。。

四角を動かすだけ(1)。

簡単なことだからこそ、きっちりやります。
本質が分かりやすいです。
そして、簡単なところ(練習)できっちり出来なければ、当然、難しいところ(本番)で出来るはずがない、と思っています。

四角を動かしてみます。

オブジェクト指向モデリングとしては典型的な駄目な例です。

package com.ukiyu.blog001;

public class Service {

	public static void main(String[] args) {
		System.out.println("Ver.001 Start");

		// 座標(100, 100)に存在する20x30の大きさの四角形
		Rectangle rectangle = new Rectangle(100, 100, 20, 30);
		System.out.println(rectangle);

		// x方向に10動かす。
		double x = rectangle.getX();
		x = x + 10;
		rectangle.setX(x);

		// y方向に20動かす。
		double y = rectangle.getY();
		y = y + 20;
		rectangle.setY(y);

		System.out.println(rectangle);
	}
}
package com.ukiyu.blog001;

public class Rectangle {
	private double x;
	private double y;
	private double height;
	private double width;

	public Rectangle(double x, double y, double height, double width) {
		this.x = x;
		this.y = y;
		this.height = height;
		this.width = width;
	}

	public double getX() {
		return x;
	}

	public void setX(double x) {
		this.x = x;
	}

	public double getY() {
		return y;
	}

	public void setY(double y) {
		this.y = y;
	}

	@Override
	public String toString() {
		return "Rectangle (x, y) = ( " + x + ", " + y + ") , (height, width) = ( " + height + ", " + width + ")";
	}
}

コンソール出力。

Ver.001 Start
Rectangle (x, y) = ( 100.0, 100.0) , (height, width) = ( 20.0, 30.0)
Rectangle (x, y) = ( 110.0, 120.0) , (height, width) = ( 20.0, 30.0)

コードはこちら↓。
https://github.com/ukiyu/blog/tree/master/ForBlog/src/com/ukiyu/blog001

さて、一体いくつのツッコミを入れることが出来るでしょうか?

とりあえず、main()関数を眺めると

  • newしてる。
  • 引数が多い。
  • getしてる。x 2
  • ローカル変数を使いまわしてる。
  • setしてる。x 2

と、すべての行にツッコミが入ります(確認用の出力は除く)。

newしてる。

まぁ、いきなり、コンストラクタってnewするでしょ?って話になりかねないのですが
書籍「Effective Java」によるとstatic ファクトリメソッドを使うことが推奨されています。
理由はいくつかあるのですが、コンストラクタが沢山ある場合、それぞれのコンストラクタは
引数が異なり、インスタンスの生成の仕方が異なります。
でも全てのコンストラクタの名前は同じなので
クラスの使用者が、引数によってどのようなインスタンスが生成されるかを判断せざるを得ません。
メソッドにすることによって適切な名称を与えることが出来、ユーザビリティ及び可読性が向上します。

	private Rectangle(double x, double y, double height, double width) {
		this.x = x;
		this.y = y;
		this.height = height;
		this.width = width;
	}

	static public Rectangle of(double x, double y, double height, double width) {
		return new Rectangle(x, y, height, width);
	}
	Rectangle rectangle = Rectangle.of(100, 100, 20, 30);

引数が多い。

引数は出来るだけ少なくしましょう。4つも並んでいると間違えちゃいます。
100, 100, 20, 30 って何の数字の羅列なんでしょうね?ぱっと見、理解不能です。
ここでは修正は後回しにします。量が多くなりそうなので。。。

getしてる。

安易にgetしないで下さい。getしようとしているものは、そのインスタンスの責任の下で管理されています。
カプセル化情報隠蔽オブジェクト指向の本質の1つです。getメソッドはそれを破壊します。
getせずにどうやって目的を達成するか、を考えるのも設計するということの一部だと思います。
getしてしまうと、お外に情報が伝搬します。すなわち、変更修正があった際の影響範囲が広がってしまうのです。
getしないことによって内側に影響を留めることが出来るようになります。

ここでは求めるな、命じよ。(Tell, Don't Ask.)という原則に従います。
(オブジェクト指向エクササイズ、9つのルールにも"getしない"ルールがあります。)

	public void addX(double x) {
		this.x += x;
	}

	public void addY(double y) {
		this.y += y;
	}
		rectangle.addX(10);
		rectangle.addY(20);

rectangleが持っている値(責任)を求めるのではなく、それに対する操作を命じるのです。

ローカル変数を使いまわしてる。

上述のaddメソッドを追加したのでいなくなっちゃうのですが、同じ名前の変数を使いまわすと混乱を招くことがあるので、
個々にきっちり名前を付けてあげましょう、というお話です。

	public int update(int current) {
		current = current + 1;
		return current;
	}

1行目のcurrentと2行目のcurrentと3行目のcurrent、一体どれが本当にcurrentなんでしょうか?

	public int update(int current) {
		int updated = current + 1;
		return updated;
	}

こうやってちゃんと正しい名前を付けてあげることでコードの可読性が向上します。
付加的な効果として、前の値が上書きされなくなるのでデバッグしやすかったりもします。

setしてる。

これも、上述のaddメソッドを追加したのでいなくなっちゃうのですが、setすることによる問題と同様の問題がまだ残っています。
その問題というのはインスタンスの状態が可変(ミュータブル)であるということ。
インスタンスは出来るだけ不変(イミュータブル)であることが望まれます。
可変なインスタンスは何かしらの問題が発覚した際に、どの状態で問題が生じて、その状態はどこで作られたのか?を調べる必要があります。そして全ての状態に対するテストが必要となります。
不変であれば、問題が発覚した際の状態が問題であることは一目瞭然です。また、スレッドセーフなので容易に扱うことが出来ます。
また、オブジェクト指向エクササイズ、9つのルールにも"setしない"ルールがあります。
修正は容易ですが、お腹いっぱいになってきたので後回しにします。

修正後。

package com.ukiyu.blog002;

public class Service {

	public static void main(String[] args) {
		System.out.println("Ver.002 Start");

		// 座標(100, 100)に存在する20x30の大きさの四角形
		Rectangle rectangle = Rectangle.of(100, 100, 20, 30);
		System.out.println(rectangle);

		// x方向に10動かす。
		rectangle.addX(10);

		// y方向に20動かす。
		rectangle.addY(20);

		System.out.println(rectangle);
	}
}
package com.ukiyu.blog002;

public class Rectangle {
	private double x;
	private double y;
	private double height;
	private double width;

	private Rectangle(double x, double y, double height, double width) {
		this.x = x;
		this.y = y;
		this.height = height;
		this.width = width;
	}

	static public Rectangle of(double x, double y, double height, double width) {
		return new Rectangle(x, y, height, width);
	}

	public void addX(double x) {
		this.x += x;
	}

	public void addY(double y) {
		this.y += y;
	}

	@Override
	public String toString() {
		return "Rectangle (x, y) = ( " + x + ", " + y + ") , (height, width) = ( " + height + ", " + width + ")";
	}
}

コンソール出力。

Ver.002 Start
Rectangle (x, y) = ( 100.0, 100.0) , (height, width) = ( 20.0, 30.0)
Rectangle (x, y) = ( 110.0, 120.0) , (height, width) = ( 20.0, 30.0)

コードはこちら↓。
https://github.com/ukiyu/blog/tree/master/ForBlog/src/com/ukiyu/blog002

この時点で気になるのは

ってところでしょうか。

続く。