神は死んだ 第一回 インスタンスの生成と性質

こんにちは。神は死んだ
なんとなく適当に作ってた個人プロジェクトがその適当さ故に大事故を起こしてしまいました。
今回はその失敗で気づいた事などを踏まえて、備忘録として、またクラス設計の指針としてここに思うことを書いておきます。
ちなみに言語はJavaになりますが、多分大体の静的オブジェクト指向言語なら参考になるような気がします。気が向いたら参考にしてください。

インスタンスの生成

ぼくが今まで使用してきたインスタンスの生成方法は、全部で三つくらいあります。

newによる生成

Object o = new Object();

一つは、ありきたりなnewを使う方法。
クラス設計をてきとーにやると確実にこの方法でインスタンスを生成すると思います。
このインスタンス生成方法は以下の点で不利です。

  1. 広範囲でnewを使用すると修正が大変
  2. コンストラクタが必要な引数が多いとイラッ☆とする
  3. 名前がないからコンストラクタの挙動が分からない

広範囲でnewを使用すると修正が大変

ソースコードが増えるにつれてリスクが増してきます。開発規模が小さかったり、使う範囲が非常に狭い場合はこの方法でも問題ありません。しかし、最初から広範囲で使用されるクラスである事が分かっているのなら、別の方法でインスタンスを生成した方がいいでしょう。
たとえば、以下のようなコードが100箇所あったとします

List pokemon = new LinkedList();

もしこのコードをLinkedListからArrayListに変更するとなると、100箇所すべてを見て変更しなければなりません。
しかもLinkedListが適切な箇所は書き換えてはならない、なんて話になった場合はエディタの自動変換は簡単に使えません。
途方に暮れながら、プログラマはコードを少しずつ追っていって修正をかけるでしょう……

コンストラクタが必要な引数が多いとイラッ☆とする

とてもとても分かりやすい例があります。
java.awt.event.MouseWheelEventのコンストラクタです。

MouseWheelEvent(Component source, int id, long when, int modifiers, int x, int y, int xAbs, int yAbs, int clickCount, boolean popupTrigger, int scrollType, int scrollAmount, int wheelRotation)

こんなのニンゲンが読むものじゃねぇ!
こんな設計した日にはぼく泣いちゃいます。


引数の多いコンストラクタは目とプログラマの精神を悪くします。
精神がおかしくなったプログラマは、MouseWheelEventを使う時にもしかしたら第五引数の「int x」に入れるべき値を間違えて第六引数の「int y」に入れているかもしれません。その後も引数の値を一個ずつずらしていれてしまうかもしれません。うまくいくと、プログラマは引数の値がずれている事に気づかずにコンパイルを行なうかもしれません。そして予期せぬバグでさらに精神をおかしくするでしょう。
引数を入れる場所を間違えてもコンパイラはエラーを教えてはくれないのです……。

名前がないからコンストラクタの挙動が分からない

最後に、コンストラクタには残念な事に名前がありません。
コンストラクタの動作は引数とコメントを頼りに予測しなければなりません。これは名前のついたメソッドより大変な作業になる事があります。
ここで、Twitter4jというTwitterAPIを楽に扱う為のライブラリにあるtwitter4j.Pagingのコンストラクタを見てみましょう。

Paging()

Paging(int page)

Paging(int page, int count)

Paging(int page, int count, long sinceId)

Paging(int page, int count, long sinceId, long maxId)

Paging(int page, long sinceId)

Paging(long sinceId)

Pagingには複数のコンストラクタがありますが、どのコンストラクタがどのような動作を行なうのかは上記を見ただけでは不明です。
これがメソッドなら、名前から動作を予測できます。toString()なら「クラスのなんらかの文字列を返す」というのが大体分かります。
しかしコンストラクタは名前もなく、またPagingの場合はコメントもないため挙動が全く分からないのです……

【おまけ】java.lang.Stringのコンストラク

String()
String(byte bytes)
String(byte
bytes, Charset charset)
String(byte ascii, int hibyte)
String(byte
bytes, int offset, int length)
String(byte bytes, int offset, int length, Charset charset)
String(byte
ascii, int hibyte, int offset, int count)
String(byte bytes, int offset, int length, String charsetName)
String(byte
bytes, String charsetName)
String(char value)
String(char
value, int offset, int count)
String(int[] codePoints, int offset, int count)
String(String original)
String(StringBuffer buffer)
String(StringBuilder builder)

ハハッ、みろ、コンストラクタがゴミのようだ!



staticなメソッドによる生成

二つ目は、staticなメソッドを使った生成です。

public class Factory
{
private Factory{}
public static Factory newInstance() {
return new Factory();
}
}

Factory f = Factory.newInstance();

大体こんな風に使います。
デフォルトコンストラクタをprivateにする事で「new Factory()」という書き方が出来ないようになっています。コンストラクタもメソッドの一種なので、privateにすると外部からアクセスできなくなります。
newが使えない代わりに、newInstance()を使ってインスタンスを生成します。privateなメソッドは内部では自由に参照できます。コンストラクタも例外ではなく、privateなコンストラクタはクラスの中でなら自由にnewを使えます。


このインスタンスの生成方法はnewより以下の点で勝ります。

  1. 修正が容易
  2. コンストラクタに名前をつける

修正が容易・コンストラクタに名前をつける

先程のコレクションも、以下のような書き方をすれば修正が容易になります。