親記事 : https://qiita.com/Regpon/items/1116679adadd8fb76f3f
多態性
上記の記事で書いた内容の続きになるが、条件分岐を減らす方法として、区分を多態にする方法がある。
C#での実装をするときに私がよくお世話になっている岩永氏の「++C++;未確認飛行 C」によると
多態性(polymorphism: ポリモーフィズム)とは、 同じメソッド呼び出し(オブジェクト指向用語的には「メッセージ」という)に対して異なるオブジェクトが異なる動作をすることを言います。
引用元:https://ufcpp.net/study/csharp/oo_polymorphism.html
とある。
多態性をうまく使えば条件分岐を減らせる?
多態性を持たせるオブジェクトのイメージ
文章だけではイメージがつかないので実際に分岐のある実装を使って多態性のあるオブジェクトを用意すると分岐がなくせるか確認する。
条件分岐を整理する で最後に残った条件分岐を以下のようなクラスで使っていたとする
class Charge {
String type;
Charge(String type) {
this.type = type;
}
// Chapter2.1で残っていた条件分岐
Price price() {
if (isStudent()) return studentPrice(); // 学生料金
if (isToddler()) return toddlerPrice(); // 幼児料金
return adultPrice(); // 成人料金
}
boolean isStudent() {
return type.equals("学生");
}
boolean isToddler() {
return type.equals("幼児");
}
boolean isAdult() {
return type.equals("成人");
}
Price sutudentPrice() {
return new Price(500);
}
Price toddolerPrice() {
return new Price(0);
}
Price adultPrice() {
return new Price(1000);
}
}
この実装において、条件分岐をしている理由はユーザー(顧客)の区分に応じて料金が異なるためである。
この条件分岐をなくす、つまりユーザーの区分(学生、幼児、成人)を多態性のあるオブジェクトにすることで実現できる。
多態性にしてみる
学生料金、幼児料金、成人料金を多態性のあるオブジェクトに変更するためにまずは共通の interface を用意する
interface Fee {
Price price();
String type();
}
この interface を実装したクラスを区分ごとに作成する。
用意するメソッドとしては、料金を返すメソッドとなんの区分かを返すメソッドを用意する。
// 学生
class StudentFee implements Fee {
Price price() {
return new Price(500);
}
String type() {
return "学生";
}
}
// 幼児
class ToddlerFee implements Fee {
Price price() {
return new Price(0);
}
String type() {
return "幼児";
}
}
// 成人
class AdultFee implements Fee {
Price price() {
return new Price(1000);
}
String type() {
return "成人";
}
}
このように、Fee という interface を実装した区分ごとのクラスを用意した。
このように実装すると、区分ごとの料金を返していた Charge クラスはどのようになるかというと、
class Charge {
Fee fee;
Charge(Fee fee) {
this.fee = fee;
}
Price price() {
return fee.price();
}
}
このように、料金を返していたメソッドから条件分岐が消えてすっきりとした。実際は区分ごとに応じた料金が買えるのだが、 Charge クラスの関心ごとから、顧客の区分がなんなのかが消え、 Fee interface を実装したオブジェクトにいくらが帰ってくるのかを委ねている。
多態性のあるオブジェクトを管理する
このようにすることで条件分岐を消すことができたが、このままでは Fee の実装クラスを使う側で、どんな区分があるのか分かりにくく、考慮しなくてはならないので区分としての振る舞いを定義するために**列挙型(enum)**を用いる。
enum FeeType {
student(new StudentFee()),
toddler(new ToddlerFee()),
adult(new AdultFee());
private Fee fee;
private FeeType(Fee fee) {
this.fee = fee;
}
Price price() {
return fee.price();
}
String type() {
return fee.type();
}
}
このようにすることで、区分ごとのロジックが整理され、どのような区分があるのかもすっきりと理解できる。仮に Charge クラスで利用するとするならば
class Charge {
Price price(String feeTypeName) {
return feeType.valueOf(feeTypeName).price();
}
}
となり、Chargeクラスが不要になったように見える。この記事の冒頭に登場したあの長ったらしいクラスを消すことができたのだ。それはつまり、Chargeクラスを利用する側がfeeTypeNameを指定することで直接的に区分ごとの料金を、区分ごとの料金算出ロジックがどうなっているか意識することなく利用できるようになったということである。
こうしておけば、区分ごとの料金算出ロジックが変更になったり、削除されたり、追加されたりしても、影響は区分ごとのオブジェクト(区分オブジェクト)に限定されるようになる。
まとめ
- else句はガード節を使って消す
- 条件の判別ロジックや処理はメソッドに切り出す
- 区分があれば区分ごとに別クラスにすることで疎結合な実装になる
- 多態を使うことで条件分岐を書かずに実装できる
- 列挙型を使うことで多態をシンプルにできる
文献
本記事は 現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の実践技法 著:増田 亨 を読んで(なるべく)自分の言葉でまとめたものです。
興味を持っていただけたらこちらの本を読んでみていただけたらと思います。(勝手に宣伝)