そもそもオブジェクト指向は現実世界に存在するモノをオブジェクトとしてプログラム上に表現し、システム全体を複数のオブジェクトの相互作用として認識する考え方です。従って、この前提に立って考えるなら、プログラム上に構築されるオブジェクトはプログラムの外の世界に存在するモノをプログラム上に再現したものでなければならないということになります。しかし、現実はXMLパーサー、バリデーター、業務ロジックというように、実在しないオブジェクト(または本来オブジェクトに内包されるべき手続き)がプログラム上にのみ存在するというケースが多々あります。これらのうちのいくつかは名前を変えることで正しいオブジェクトになる可能性があります。例えばXMLパーサー(XmlParser)はパース可能なXML(ParsableXml)に改名することで正しいオブジェクトになります。
正しいオブジェクトへの改名の例
| 元の名前 | 改名後の名前 | |
| FormValidator | → | ValidatableForm |
| FileReader | → | ReadableFile |
クラス設計を行う際は、そのクラスのインスタンスが指し示すモノは何か考えるようにしましょう。もしそのクラスのインスタンスが表現するモノが現実世界に存在しないのならば、そのクラス設計は間違っています。
良いオブジェクトは不変(immutable)であるべきです。これはオブジェクトが持つ状態は後から変更できないという意味ではありません(よくこのように誤解されますが)。一度生み出されたオブジェクトは、それが指し示す実体から離れないというのがimmutableの意味です。イメージが沸きづらいので例を挙げて説明しましょう。
String str = "ABC";
str.toLowerCase();
System.out.println(str);
最初に少し解説すると、toLowerCaseメソッドは文字列を小文字にするメソッドです。従って、2行目のstr.toLowerCase()によって"abc"という文字列が得られます。それでは、3行目で出力される文字列は何でしょうか?答えは"ABC"です。toLowerCaseメソッドの呼び出しによってStringオブジェクトが表現する"ABC"という文字列の実体は変更されません。同じように大文字にするtoUpperCaseメソッドや文字列の特定の部分だけ切り出すsubstringメソッドも、それらの実行によって得られる文字列は元の文字列とは異なりますが、これは元のStringオブジェクトが指し示す文字列に影響を与えません。従って、Stringオブジェクトは不変オブジェクトです。不変オブジェクトの真意は、それが指し示す実体を外から変更してはならない、という点にあります。
オブジェクトが持つパブリックメソッドは全てインタフェースで定義されたものであるべきです。それ以外のパブリックメソッドは持つべきではありません。と言うよりも、オブジェクト指向におけるオブジェクトは他のオブジェクトからの何らかの要求に応じて機能を提供するものであり、この文脈における「何らかの要求」とは紛れもなくインタフェースに定義されたメソッドのことです。あるオブジェクトが欲する機能はインタフェースとして表現される以上、要求される側のオブジェクトが公開するパブリックメソッドは必然的にインタフェースを実装したものになるはずです。
上の図を見てください。従業員オブジェクトは会社オブジェクトの要求に応えるべく雇用契約に従い、顧客オブジェクトの要求に応えるべく商品販売契約に従います。実際のところ、雇用契約と商品販売契約に従うオブジェクトに期待されるのは、それぞれの契約で規定されたメソッドを実装することのみです。それ以外の公開メソッドは誰からも期待されていません。誰からも期待されていないメソッドをパブリックにしておく理由はありません。
自分自身と外部のオブジェクトの関係性で言うと、インタフェースによって期待されないパブリックメソッドは(誰からも利用されないと言う意味で)無意味ですが、自分自身や自分のサブクラスが利用するメソッドもあり得ます。このような場合はprivateやprotectedなどで適切にアクセス制御を行うようにしましょう。
良いオブジェクトはスタティックなメソッドや変数を持ちません。そもそもスタティックなメンバーはオブジェクトに内包されるものではなく、クラスに付随するものです。従ってクラスローダーによってクラスがメモリ上にロードされた瞬間からどこからでも利用することが可能です。勘の良い人は気付いたかもしれませんが、スタティックメンバーは本質的にスレッドセーフではないということです。スタティックメンバーは本来オブジェクト指向には必要のない概念です。
しかし、実際の開発現場ではユーティリティクラスと称して、インスタンスすることなく利用できる大量のスタティックメソッドを持ったクラスをよく目にします。ユーティリティクラスはその名前のとおり様々なメソッドを提供しますが、よくあるのはログ出力や文字列操作など、どの機能にもそれなりに需要のある機能(いわゆる横断的関心事)を提供しているユーティリティクラスです。
packege foo.bar.baz;
public class Logger {
public static void info(String message) {
// ログ出力処理
}
}
なぜどこからでも利用できるようにしておく必要があるのでしょうか。それはいつ誰が使うか分からないからです。それ以外に理由はありません。ただ、それにしても他にやりようがないのでしょうか。いいえ、そんなことはありません。ログ出力が必要な全てのクラスの基底クラスにログ出力処理を(もちろんfinalで)記述してしまえば良いのです。実に簡単な話です。
オブジェクト指向にスタティックメンバーは必要ありません。適切なクラス設計を行うことで、スタティックメンバーをシステムから追放してください。