はじめに
本記事は設計の超初心者である筆者がシーケンスを書こうとしてつまずいたこと、そこから学んだことについて書いたものです。
「初心者、そんなところでつまずくの!?」と思われるかもしれませんが、広い心でご笑覧ください。間違っていたら教えていただけると幸いです。
つまずき~何をライフラインにしたらいいの?~
仕事でシーケンス図を作成する機会があり、さっそくシーケンス図の書き方を調べて書くこととした。
当時のシーケンス図のイメージは「クラスAがクラスBのメソッドbを呼ぶとしたら、クラスAからクラスBに矢印を引いて、メソッドbというメッセージをつける」という程度。
書き方を調べた時もだいたいそんな感じのシーケンス図がよく見つかった。
しかし、自分が書くべきシーケンスには、インタフェースとその実装のクラスがあった。インタフェースだけでなく、継承関係にあるクラスもあった。拡張メソッドのクラスもあった。調べて出てきた例には当てはまらなさそうだ。
「クラスAがクラスBのメソッドbを呼ぶけど、メソッドbはクラスBのインタフェースに定義されてるから実際に呼ばれてるのはインタフェース?じゃあインタフェースをシーケンス図に書けばいいのかな?」
「クラスCのメソッドcはクラスCにしかないけど、メソッドdはクラスCの基底クラスのクラスDにある、この場合クラスDもシーケンス図に書かないといけないのかな?」
「クラスEにはメソッドfっていう拡張メソッドがあるから、拡張メソッドを実装してるクラスFもシーケンス図に登場するよね、だってメソッドf呼ばれてるし。」
上記の発想をそのままシーケンス図で表すと、以下のようになる。
確かにシーケンスに関わるクラスは全て登場しているが、ごちゃごちゃしていて読みづらく、設計の良し悪しが評価できるような状態ではない。
ライフラインに書くべきはオブジェクトである
シーケンス図のライフラインで書くべきは何か。
そもそもシーケンス図とは、オブジェクトの相互作用を時間軸に沿って表現する図である。つまり、ライフラインに書くべきはオブジェクトであり、今回のようにクラス間の相互作用を書く場合には、クラスを具象化したものであるインスタンスを書くべきである。
つまり、
「クラスAがクラスBのメソッドbを呼ぶけど、メソッドbはクラスBのインタフェースに定義されてるから実際に呼ばれてるのはインタフェース?じゃあインタフェースをシーケンス図に書けばいいのかな?」
インスタンスになるのはインタフェースではなくその実装であるため、図に書くべきはクラスBのインスタンスBになる。
「クラスCのメソッドcはクラスCにしかないけど、メソッドdはクラスCの基底クラスのクラスDにある、この場合クラスDもシーケンス図に書かないといけないのかな?」
実際のインスタンスの型はクラスCになる。図に書くべきはクラスCのインスタンスC。
「クラスEにはメソッドfっていう拡張メソッドがあるから、拡張メソッドを実装してるクラスFもシーケンス図に登場するよね、だってメソッドf呼ばれてるし。」
拡張メソッドも結局はクラスEにメソッドが追加されていることと同義であるため、図に書くべきはクラスEのインスタンスE。
そしてできあがったシーケンス図が以下である。
以前の図よりもクラス同士の関係がわかりやすくなった。
もちろん、設計の問題も見つけやすい。
図の中でいえば、メソッドfが複数回呼ばれているなど。
でもクラス書きたいときもありますよね?
staticなクラスはインスタンス化できない。インスタンスでないものをシーケンス図にはどうやって表せばいいのか。
シーケンス図のルールとしては、下線がない場合はクラスを表す。
また、「:」の左側にはオブジェクトの名前を書く。オブジェクトの名前とクラスの名前はどちらか一方でもよい。
おわりに
仕事でごちゃごちゃしたシーケンス図を作成してしまった経験から、この記事を書こうと思い立った。
「登場するクラスは全部書かなきゃ!」と思い込んで図の作成を行ってしまったが、不必要に複雑な図となり設計で議論したい部分がわかりにくくなってしまっていたうえ、そもそもシーケンス図の書き方からも外れたものになっていた。
シーケンス図に限らず、UMLを使うときは「何をその図で表現したいか」を考えて使うべきと学んだ。
参考
『実践UML パターンによる統一プロセスガイド 第2版(Craig Larman 著、依田 光江 訳)』
実はGRASPの学習のために購入した書籍。(以前の記事参照。)