- PR -

StateパターンでのInterfaceの使用について

投稿者投稿内容
Micky
大ベテラン
会議室デビュー日: 2002/09/04
投稿数: 137
投稿日時: 2005-01-13 18:40
Mickyでございます。
今年もよろしくお願いいたします。

さて、参照元記事では「Stateパターン」の
StateクラスにInterfaceを使用していますよねえ?

これって、何かメリットなり理由があるのでしょうか?
自分だと単純に普通のクラスにして、
各状態を継承で作ってしまうと思うんですよ(^^;

以前、「継承、Interface、Delegate」の使い分けについて
質問させていただき、なんとなくわかってきたつもりでは
いたのですが、又悩んでしまいました。

「今更何言ってんだ!」と言う声も聞こえてきそうですが、(^^;
ご助言いただけたら幸いでございます。

よろしくお願いします。


yukitos
会議室デビュー日: 2004/12/09
投稿数: 6
投稿日時: 2005-01-13 22:09
例えば次のようにしてInterfaceではなく、クラスを継承させてみます。
コード:

class C1
{
public void Method1() {...}
public void Method2() {...}
}
class C2 : C1
{
// Method1
// Method2
public void Method3() {...}
}
class C3 : C2
{
// Method1
// Method2
// Method3
public void Method4() {...}
}
class C4 : C3
{
// Method1
// Method2
// Method3
// Method4
public void Method5() {...}
}


さてこの時、クラス C4 には Method3 を持たせたくない場合どうしましょう。
Method1,2 は C1 にあるので C1 を継承すればよいのでしょうが、
Method4 は C3 にあります。
ただし C3 は C2 を継承しているので、必然的に Method3 がついてきてしまいます。

そこで、継承を使わずにInterfaceを使おうということになります。
コード:

interface IInterface1
{
void Method1();
void Method2();
}
interface IInterface2
{
void Method3();
}
interface IInterface3
{
void Method4();
}
interface IInterface4
{
void Method5();
}

class C1 : IInterface1 {...}
class C2 : IInterface1, IInterface2 {...}
class C3 : IInterface1, IInterface2, IInterface3 {...}
class C4 : IInterface1, IInterface3, IInterface4 {...}


こうすると C4 は確かに Method1,2,4,5 と、Method3 以外を実装することができます。

実のところ僕自身もまだ継承とInterfaceの使いどころがなかなか区別つきませんが、
機能拡張は継承、機能追加はInterfaceという感じでしょうか。

--
追記:
せっかくの参照元記事をあげていただいているのに
抽象的な例をあげてしまってすみません…。

[ メッセージ編集済み 編集者: yukitos 編集日時 2005-01-13 22:11 ]
tsune
会議室デビュー日: 2002/07/09
投稿数: 15
お住まい・勤務地: 兵庫県西宮市
投稿日時: 2005-01-13 22:09
こんにちは、中西です。

>自分だと単純に普通のクラスにして、
>各状態を継承で作ってしまうと思うんですよ

基本となる状態を普通のクラスで表現した場合、
その派生クラスとなる状態が存在しますね。
現在だけを視野におけばこれでも良いように思えます。

ただ、少し長い目で見た場合はどうでしょう。
Stateは状態と名が付くだけに変化しやすい部分です。

基本の状態を細分化して複数の状態に分割しなければいけなくなったり、
基本の状態自体が変更されたり、必要でなくなる可能性は考えられないでしょうか。
そうなると変更の影響が各派生クラスにも及ぶことになります。
またStateの呼び出し元への影響もあるでしょう。

このような変更に対する影響を少なくする鍵は「抽象」にあります。
Stateの呼び出し元と各Stateの間に抽象を挟み、
ワンクッション置いてやればStateの呼び出し元はその抽象だけに依存します。
新しい状態の追加や既存の状態の変更に対する影響は、
対象となる状態クラスだけにとどまります。

また、各状態に共通部分があるならば、
抽象の表現をインターフェイスから抽象クラスへと切り替えて
共通部分を抽象クラスへ引き上げてやればよいでしょう。
このリファクタリングはそんなに手間のかかるものではありませんよね。
tak3
ベテラン
会議室デビュー日: 2004/04/15
投稿数: 80
お住まい・勤務地: 菜の花・銀杏
投稿日時: 2005-01-14 11:35
わざと、おかしな例を出しているのかもしれませんが、
継承について勘違いする人が出るかもしれませんので一言
引用:

(中略)
さてこの時、クラス C4 には Method3 を持たせたくない場合どうしましょう。
Method1,2 は C1 にあるので C1 を継承すればよいのでしょうが、
Method4 は C3 にあります。
ただし C3 は C2 を継承しているので、必然的に Method3 がついてきてしまいます


これは、継承のまちがった使い方です。
継承は is-a の関係を保つべきです。
#(C3 is C2)であり、(C4 is C3)なら(C4 is C2)となっても不思議でありません。
# 実際に、C4をC2にキャストできるのに、Method3が呼べないのは問題です。

Method3が必要ないのであれば、そもそも継承するのか?を検討するべきです。
実装済みの機能だけ使いたいなら、継承でなく委譲を使います。
----
ちなみにStateパターンなら私も同様の実装をすると思います。
あ、正確には「普通のクラス」でなく「抽象クラス」ですね。
引用:

自分だと単純に普通のクラスにして、
各状態を継承で作ってしまうと思うんですよ

Micky
大ベテラン
会議室デビュー日: 2002/09/04
投稿数: 137
投稿日時: 2005-01-14 13:31
Mickyでございます。

ご助言いただいたみなさまありがとうございました。

そして!
申し訳ありません。なんだか曖昧な表現をした為に混乱させて
しまったようです。m(__)m

tak3さんがご指摘の様に、わたしの意図は

「普通のクラス」=「抽象クラス」

となります。

つまり、最初の投稿の該当部分は
「自分ならInterfaceを使用せずに、通常の抽象クラスを使用する」
となるとご理解ください。

と言うことで、改めて質問します。
「参照記事」では通常の抽象クラスを使用せずにInterefaceを使用しているのには
なにか理由があるのでしょうか?

自分の理解するInterefaceは…

「Is-a」関係の成り立たないクラス同士に共通のインターフェースを提供する

というモノです。

Stateパターンの「抽象Stateクラス」と各「ConcreatStateクラス」には
「Is-a」関係が成り立っていると思うので、あえてInterfaceを使用するには
それなりの理由があるのでは?と考えたわけなんですね。

と言うことで、引き続きお気付きの点等ございましたら、
ご助言いただけたらと思います。

よろしくお願いします。
yukitos
会議室デビュー日: 2004/12/09
投稿数: 6
投稿日時: 2005-01-14 14:48
yukitosです。
引用:

tak3さん:
わざと、おかしな例を出しているのかもしれませんが、
(略)
Method3が必要ないのであれば、そもそも継承するのか?を検討するべきです。
実装済みの機能だけ使いたいなら、継承でなく委譲を使います。


すみません。おかしな例を提示してしまってよけいな混乱を招いてしまいました。
確かに継承の必要があるかどうか、がまず検討されますね。
C4 の C2 へのキャストについてもされたくない、という意味を
含めたつもりだったりしますが最早どうでも良い話です

引用:

Mickyさん:
Stateパターンの「抽象Stateクラス」と各「ConcreatStateクラス」には
「Is-a」関係が成り立っていると思うので、あえてInterfaceを使用するには
それなりの理由があるのでは?と考えたわけなんですね。


なるほど質問を把握していませんでした。すみません。
言われる通り、確かに抽象クラスとしてやればよさそうです。
何か利点などあるのかなと思い、MSDNで「抽象クラスとインターフェイスに関する推奨事項」
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/vbcon/html/vbconabstractclassesversusinterfaces.asp
など読んでみましたが実のところよくわかりませんでした
tsune
会議室デビュー日: 2002/07/09
投稿数: 15
お住まい・勤務地: 兵庫県西宮市
投稿日時: 2005-01-14 15:18
中西です。

引用:

tak3さんがご指摘の様に、わたしの意図は

「普通のクラス」=「抽象クラス」

となります。



なるほど。そういうことでしたか。
意図をうまく汲み取れず、失礼しました。

さて、記事のStateパターンのサンプルコードは、
リファクタリングの結果として導かれたコードです。

リファクタリング前のコードから状態を取り出す過程で、
各状態の実装には共通部分がなかったため、
抽象クラスにする必然性がありませんでした。

もし各状態になんらかの共通部分があれば
同じコードは2度書かないというルールに則り、
抽象クラスを使ったと思います。

しかしインターフェイスか抽象クラスかの選択は難しい局面が多く、
yukitosさんのご紹介にもあるように様々な指針やトレードオフがありますよね。
一概にはどちらの選択が正しいとは言い切れないと思っています。

個人的にはシンプルに考えて、
「実装を持たない抽象クラスは使わない」
というのもひとつの指針ではないかと思います。

この指針に従って抽象クラスは極力具体的な実装と
抽象メソッドが混在する場合のみに使用するようにしています。
このあたりはTemplate Methodパターンが参考になるでしょう。
Micky
大ベテラン
会議室デビュー日: 2002/09/04
投稿数: 137
投稿日時: 2005-01-14 15:43
Mickyでございます。

中西さん…

大変わかりやすい説明ありがとうございました。

本文中には現れない部分でも、
指針に基づきリファクタリングされた結果なのですね。

う〜〜ん…凄い…
大変勉強になりました。

「単なる例」として捉えていたので、そこまでの意図を
汲み取る事が出来ませんでした。m(__)m
やはり記事を書くというのは大変な労力なのですね。

該当記事がTDDを使用している部分でも大変興味深く
読ませていただいております。

更に、デザインパターン適用へのリファクタリングだけでなく、
他の部分でもリファクタリングが実施されていると言うことが判ったことは、
今後の記事を読む上で、技術者心理をくすぐられた気分で楽しみが増えましたよ(^^)

次回からも期待しております。
ありがとうございました。

スキルアップ/キャリアアップ(JOB@IT)