スペシャルケース(SpecialCase)パターン(PofEAA)
SQLのNULLや浮動小数計算のNaNのような存在をサブクラスとして定義するパターン。
SQLのNULLはカラムのドメイン(定義域)から外れた値のようなものとして存在し、NaNも浮動小数点数から外れた値のようなものとして存在している。数学っぽくいえば、本来とりうる値の集合Sがあって、そこから外れた存在としてNULLやNaNがある(補集合の要素ともいえない存在)。オブジェクトにおいても、本来とりうるインスタンスの集合があって、そこから外れた存在もインスタンスとして扱いたい場合がある。
SQLのNULLのありがちな使われ方がわかりやすいので、これを例に使う。
NULLにはさまざまな意味がある。たとえば、以下の4つが考えられる。
(1)失われた値
(2)未知の値
(3)雑多な値
(4)適用不能な値
(中略)私がイースターエッグを探していて、その色を知りたがっている、という例を考えてみよう。穴の中に卵が落ちてしまっている場合、卵は失われてしまっている(1)——あとで取り戻せれば良いのだが。次に、卵がアルミフォイルでくるまれている場合、卵はあるのだが何色かはわからない(2)。さまざまな色が混合している卵の場合、その色は一位な値に決まりきらない雑多な値を持つ(3)。もし私が見つけたのが卵ではなくビリヤードの玉だった場合、その色は適用不能である(4)。
プログラマのためのSQL 第4版 p.68
会員特典に関するドメインロジックを持つクラスMembershipBenefit
があったとして、特典を受ける権利を剥奪されたユーザーや、未入金のため特典を適用して良いか不明なユーザー、特典が適用できない匿名ユーザーなど個別処理が必要なケースを考える。
通常ケース以外はMembershipBenefit
のインスタンスを取得する時にnilを返して呼び出し元で特殊ケースに対処する方法も取れるが、呼び出し元のコードが複雑化するし複数箇所で利用されていれば、同じコードがばら撒かれることになる。MembershipBenefit
の各メソッドで特殊ケースをサポートする条件分岐をつけることもできるが、特殊ケースの数が増えたり処理内容が複雑化すれば、手に負えない状況になる。
そのため、以下のようにMembershipBenefit
を継承した特殊ケース用のクラスを作る方法をとる。
class MembershipBenefit
end
# 失われた状態の会員特典
class LostedMembershipBenefit < MembershipBenefit
end
# 未知状態の会員特典
class UnknownMembershipBenefit < MembershipBenefit
end
# 適用不能状態の会員特典
class NotApplicableMembershipBenefit < MembershipBenefit
end
ただ、継承を使っている以上、MembershipBenefit
クラスに追加したメソッドをオーバーライドし忘れて不具合が起きる可能性がある。継承関係を持たずに同じインターフェースを実装するクラス群とした方が良いだろう。部分的に差し替える必要がある場合も、継承せずに差し替える部分をStrategyパターンにして、Strategyクラスをスペシャルケースで実装する方法で回避できる。継承よりもコンポジション(委譲)も参照。