オブジェクト指向によるクラス設計
クラス設計とは
今回のテーマは「クラス設計」です。クラス設計とは、設計書などに記載されている要件からクラス候補を抜き出し、クラスの属性と操作を決めて、クラス間の関連性を設計することを指します。
属性とはクラスのインスタンスが持つデータ定義のことです。クラスのフィールド、つまり変数にあたります。操作とはクラスのインスタンスの振る舞い(機能)のことです。クラスのメソッドにあたります。
ちなみに、クラス設計をしなくてもプログラムは動きます。例えばクラスを1つだけ用意し、処理の全てをそのクラスに書いても期待する動きをします。
しかし、それが得策ではないのは、実際の業務では仕様変更や機能追加、障害対応などが頻繁に発生するためです。クラス設計を行わずにクラス1つで全ての処理を行おうとした場合(クラスが1つというのは極端な例ですが)、当然ながらクラスのソースコード量は肥大化し、if文などの条件分岐もコードのあちこちに現れ、非常に可読性が落ち、結果として修正工数が膨らむという事態になります。
クラスを作った担当者がチームにいれば工数が膨らむ程度で済みますが、担当者がチームから離れた場合、引き継いだ社員がソースコードを正しく理解できずに、バグを埋め込んだまま納品してしまう可能性もあります。
したがって、クラス設計をしっかりと行い、クラス間の結合度を下げたり、プログラムの再利用性を高めたりしてプログラムを修正しやすく、またバグの原因も特定しやすくするべきです。
クラスの属性と操作
クラスの属性と操作の話に戻りますが、例えば車クラスについて考えてみます。車が持つ属性は「色」「乗車定員」「排気量」など、操作は「前に進む」「止まる」「クラクションを鳴らす」などが考えられます。
会社員クラスなら属性は「社員名」「社員番号」「所属」など、操作は「出社する」「退社する」「仕事をする」などです。
クラス間の関連性(is-a関係とhas-a関係)
次にクラス間の関連性について解説します。クラスの関連性とは、文字通り「クラスとクラスがどのような関係性を持っているか」ということです。
クラス間の関係性は大きく「is-a関係」と「has-a関係」の2つに分けることができます。クラス設計ではクラス間が疎結合で依存性のない関係性を目指しますが、依存性のない、つまり汎用性、拡張性の高い疎結合な作りはis-a関係よりhas-a関係になります。
is-aは「A is a B.」、つまり「AはBの一種でAクラスからBクラスを派生する」というクラス間の継承関係を指します。
プログラムで書くと、次のようになります。
public class A extends B {
}
一方のhas-aは「A has a B.」、つまり「AはBを含む」という包含関係を指します。
プログラムで書くと、次のようになります。
public class A {
B b;
}
継承は親クラスの機能をそのまま使えるようになるため非常に便利です。例題のように「class A extends B」とした場合、子クラスであるAは親クラスであるBのフィールド(変数)とメソッドにアクセスできるようになります。
継承は非常に便利ですが、それ故に欠点があります。親クラスと子クラスの間にはインターフェースを挟むことができず、密結合な関係になってしまうことです。親クラスを修正すると、その影響が子クラスにも及ぶことになり、親クラスと子クラスが一心同体のような作りになってしまいます。
インターフェースは、下の例のようにロジックを記載しません。メソッドを定義するだけです。
interface Human
{
void walk();
void eat();
void sleep();
}
インターフェースはメソッドの定義だけなので、ロジックはインターフェースを実装したクラスに書くことになります。
class Tanaka implements Human {
void walk() {
System.out.println(“時速5キロで歩く”);
}
:
:
インターフェースを利用することにより、メソッドの処理内容が異なるプログラムを交換可能な作りにできます(インターフェースを使った交換可能な作りは、次回以降のデザインパターンの中で説明します)。
包含は継承のように親クラスの機能をそのまま利用はできませんが、包含したクラスに処理を委譲することで機能を使う(処理を任せる)ことができます。
public class A {
B b = new B();
void method1() {
b.method2(); // Bクラスのメソッドに処理を委譲
}
}
また、包含するのはクラスではなく、包含するクラスが実装するインターフェースにすることで疎結合の作りにできます。
インターフェースを使用すればクラスを交換できる作りになるため、is-a関係よりhas-a関係、言い換えると継承より処理の委譲ができる包含が汎用性、拡張性の高い疎結合な作りになります。継承と包含のどちらでも良い場合は包含をお勧めします。
ちなみに、継承より包含の方が疎結合になるからといって、やみくもに包含にすれば良いという訳ではありません。
Javaはオブジェクト指向の言語です。前回も述べたように、オブジェクト指向は「現実世界のモノ(オブジェクト)をそのままモデリングしたもの」なので、クラスとクラス(モノとモノ)を継承(is-a関係)にすべきか、包含(has-a関係)にすべきかは、現実世界のモノの関係に沿っていなければなりません。
例えば、車クラスとカローラクラス、プリウスクラスがあるとします。この3つのクラスは継承(is-a関係)にすべきでしょうか。包含(has-a関係)にすべきでしょうか。
継承(is-a関係)は「AはBの一種である」ですから、当てはめると「カローラは車の一種である」「プリウスは車の一種である」。言葉として成り立ちますよね。したがって、車クラスとカローラクラス、プリウスクラスは継承関係となります。
では、車クラスとハンドルクラス、タイヤクラスは継承(is-a関係)と包含(has-a関係)のどちらでしょうか。
まず、継承(is-a関係)で考えてみましょう。「ハンドルは車の一種である」。文章として違和感があるので、どうやら現実世界のモノの関係に沿っていないようですね。
次に包含(has-a関係)で考えてみます。「AはBを含んでいる」ですから、当てはめると「車はハンドルを含んでいる(持っている)」「車はタイヤを含んでいる(持っている)」。こちらは文章としてしっくりくるので、包含(has-a関係)ですね。
このように、オブジェクト指向は現実世界のモノの関係に沿ってクラス設計をします。「オブジェクト指向」という名前だけ聞くと何やら難しい気がしますが、私たちが普段見ている世の中の作りそのままなので、むしろオブジェクト指向は取っつきやすい考え方です。
例題でクラス設計の理解を深める
さて、ここまでの解説でクラス設計がある程度理解できたと思いますので、今度は例題でクラス設計に挑戦してみましょう。
「A社には社員として鈴木さん、佐藤さん、高橋さんがいます。3人は朝起きてから身支度をして、通勤電車に乗り、現場で仕事をします」
この文章をクラス設計します。クラス設計は、まず文章からクラス候補となる名詞を拾っていきます。「A社」「社員」「鈴木」「佐藤」「高橋」がクラス候補になりそうです。
「身支度」「通勤電車」「現場」「仕事」も名詞ですが、これらは「身支度をする」「通勤電車に乗る」「現場で仕事をする」というように、どちらかというと動詞になりそうです。
メソッドは動詞を拾っていくので、「身支度をする」「通勤電車に乗る」「現場で仕事をする」はメソッド候補になります。
まず、「A社」「社員」「鈴木」「佐藤」「高橋」の5つの名詞をクラス設計していきましょう。前述したように言葉にすれば分かりますね。「A社は社員を含んでいます(社員がいます)」。したがって、包含関係となります。
では「社員」と「鈴木」「佐藤」「高橋」はどうでしょう。「鈴木は社員(の一種)です」「佐藤は社員です」。したがって、継承関係です。
なお、A社は会社の一種ですが、例題文には会社という言葉は登場しませんでした。そこで「会社」もクラスとして登場させ、A社と継承関係にします。
以上をクラス図にすると次のようになります。
メソッド候補である「身支度をする」「通勤電車に乗る」「現場で仕事をする」の動詞もクラス図に反映します。
さて、これで一応クラス図は完成しましたが、これを何も考えずにコーディングすると、まず「鈴木」「佐藤」「高橋」の各クラスはそれぞれ「身支度をする」メソッドを呼び、次に「通勤電車に乗る」メソッドを呼び、最後に「現場で仕事をする」メソッドを呼ぶ作りになります。
この次々にメソッドを呼ぶ一連の処理を各クラスに任せるのは冗長な感じを受けます。一連の処理の流れをどこかに任せられればコードがすっきりしますよね。
ちなみに、この問題はデザインパターンのTemplate Methodパターンで解決します。デザインパターンとは「定石となる手法をパターン化したもの」です。先人たちの知恵ですね。
おわりに
いかがでしたか。今回はクラスの属性と操作やクラス間の関連性(is-a関係とhas-a関係)をもとに、オブジェクト指向でのクラス設計について解説しました。
次回はTemplate Methodパターンなど、デザインパターンについて解説します。