C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

--/--/-- --:--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
タグ:
トラックバック(-) | コメント(-) | このエントリーを含むはてなブックマーク
2007/05/27 23:11
"物品" と "ケース" というクラスがあります。
"物品" は "CD" や "ペン" などの総称です。"ケース" は "物品" をなんでも格納できます。
"物品" は抽象的なものですが、"ケース" は具体的なものです。
さらに、"CD" だけを格納できる "CDケース" と、"ペン" だけを格納できる "ペンケース" があります。

この時、"物品" と "CD" の間に継承関係は成り立つでしょうか?また、"ケース" と "CDケース" の間に継承関係は成り立つでしょうか?


継承関係とは 「is - a」 の関係です。つまり、「"CD" は "物品" である」と言えるなら、"物品" と "CD" の間に継承関係が成り立ちます。「"CDケース" は "ケース" である」 と言えるなら、"ケース" と "CDケース" の間に継承関係が成り立ちます。

この2つはどちらも成り立ちそうな気がします。しかし、実際成り立つのは "物品" と "CD" の継承関係だけです。"ケース" と "CDケース" の継承関係は成り立ちません。

オブジェクト指向設計における原則の一つに「リスコフの置換原則」と言う原則があります。「基本クラスのインスタンスの代わりに派生クラスのインスタンスを使用できなければならない」といったものです。
" ケース" は "CD" 以外の "物品" も格納できる必要がありますが、"CDケース" には "CD" しか格納できません。よって、"CDケース" のインスタンスは "ケース" のインスタンスの代わりにはなれません。これではリスコフの置換原則に違反してしまいます。違反しているということは 「"CDケース" は "ケース" ではない」 ということになります。


図 1. "CD ケース" は "ケース" ではない

普通に考えて、「CDケースはケースではない」 なんて、おかしな話です。なぜこんなことになってしまうのでしょうか?
実は、"ケース" の定義に誤りがあります。"ケース" は "物品" をなんでも格納できるのではなく、「なんらかの "物品" 」 を格納できるものであるべきなのです。"物品" をなんでも格納できるのは "ケース" ではなく "物品ケース" というような別のクラスにするべきです。
言い換えると、「"ケース" という名前は適切じゃないから "物品ケース" に変更して、"ケース" という基本クラスを別に用意しよう」 ということです。 ( 物品ケースという名前も不適切だと思う場合は、 "万能ケース" などで読み替えてください。 )


" ケース" は 「なんらかの "物品" 」 を格納できます。この 「なんらかの "物品" 」 というのは派生クラスで定められます。"物品ケース" なら全ての "物品" 、"CDケース" なら "CD" となります。扱う "物品" が定まらないため、"物品" ( 及びその派生クラス ) と "ケース" の間には関連を結びません。"ケース" の派生クラスがそれぞれ関連を持つことになります。

図 2. "CD ケース" は "ケース" である

"ケース" は 「なんらかの "物品" 」 を格納できるのだから、コード 1-1 のように 「なんらかの "物品" 」 の格納を補助する機能・仕組みを備えることはできます。

コード 1-1. なんらかの"物品"の格納補助機能を備えた "ケース"
using System;
using System.Collections.Generic;
using System.Text;

public abstract class ケース
{
    private readonly List<物品> 格納物品コレクションフィールド;

    protected List<物品> 格納物品コレクション
    {
        get
        {
            return this.格納物品コレクションフィールド;
        }
    }

    protected ケース()
    {
        this.格納物品コレクションフィールド = new List<物品>();
    }

    protected void 物品の格納を補助(物品 対象物品)
    {
        this.格納物品コレクションフィールド.Add(対象物品);
    }

    protected 物品 物品の取り出しを補助(int 格納位置)
    {
        物品 取り出す物品 = this.格納物品コレクションフィールド[格納位置];
        this.格納物品コレクションフィールド.Remove(取り出す物品);
        return 取り出す物品;
    }
}


これを継承した "CDケース" が コード 1-2 になります。

コード 1-2. コード 1-1 の "ケース" を継承した "CDケース"
using System;
using System.Collections.Generic;
using System.Text;

public class CDケース : ケース
{
    public CD this[int 格納位置]
    {
        get
        {
            return (CD)this.格納物品コレクション[格納位置];
        }
    }

    public CDケース()
        : this(null)
    {
    }

    public CDケース(List<CD> 格納CDコレクション)
        : base()
    {
        if (格納CDコレクション != null)
        {
            foreach (物品 item in 格納CDコレクション)
            {
                base.格納物品コレクション.Add(item);
            }
        }
    }

    public void CDを格納(CD 対象CD)
    {
        base.物品の格納を補助(対象CD);
    }

    public CD CDを取り出す(int 格納位置)
    {
        return (CD)base.物品の取り出しを補助(格納位置);
    }
}


また、ジェネリックを利用すれば派生クラスはさらに簡単に作成できます。

コード 2-1. なんらかの"物品"の格納補助機能をジェネリックとして備えた "ケース"
using System;
using System.Collections.Generic;
using System.Text;

public abstract class ケース<T>
    where T : 物品
{
    private readonly List<T> 格納物品コレクションフィールド;

    protected List<T> 格納物品コレクション
    {
        get
        {
            return this.格納物品コレクションフィールド;
        }
    }

    public T this[int 格納位置]
    {
        get
        {
            return this.格納物品コレクション[格納位置];
        }
    }

    protected ケース()
        : this(new List<T>())
    {
    }

    protected ケース(List<T> 格納物品コレクション)
    {
        this.格納物品コレクションフィールド = 格納物品コレクション;
    }

    public void 物品を格納(T 対象物品)
    {
        this.格納物品コレクションフィールド.Add(対象物品);
    }

    public T 物品を取り出す(int 格納位置)
    {
        T 取り出す物品 = this.格納物品コレクションフィールド[格納位置];
        this.格納物品コレクションフィールド.Remove(取り出す物品);
        return 取り出す物品;
    }
}


これを継承した "CDケース" が コード 2-2 になります。

コード 2-2. コード 2-1 の "ケース" を継承した "CDケース"
using System;
using System.Collections.Generic;
using System.Text;

public class CDケース : ケース<CD>
{
    public CDケース()
        : base()
    {
    }

    public CDケース(List<CD> 格納CDコレクション)
        : base(格納CDコレクション)
    {
    }
}



ちなみに、物品側から親ケースを参照できるようにしようとした場合どうなるでしょうか?
" 物品ケース" は "物品" ならなんでも格納できるのだから、"物品" には親物品ケースというプロパティを持たせ、さらに、"CD" は親CDケース、"ペン" は親ペンケースというプロパティを別に持つ必要があります。ただ、これでは "CD" が "物品ケース" に格納されている場合と "CDケース" に格納されている場合があることになります。これでは複雑な設計となってしまうため、"物品" には親ケース プロパティを用意せず、"物品" の各派生クラス ( "CD", "ペン" )がそれぞれの親ケース プロパティを持つだけにした方がいいかもしれません。あるいは、なんでも格納できる "物品ケース" というある意味特別なクラスを元々用意しなければ、このような問題は起きず綺麗な設計になります。
なお、"物品ケース" をジェネリッククラスにした場合、 "物品" も同じようにジェネリックで親ケースを指定できるようにする、ということはできないことに注意します。これは、ジェネリックの型制約の指定がうまくできないためです。
→  解決方法がありました。詳細は C#と諸々 - ミラー階層とジェネリック を参照してください。


図 3. "物品ケース" を用意しない場合



最後になりましたが、このように複数のクラス階層間において、各クラス毎に同じような関連を結ぶモデルを、ミラー階層と呼びます。


[ 参考書籍 ]
オブジェクト開発の神髄



ちょっと古い記事へのコメントですいません。
ちょうど、私のブログでも近々リスコフの置換原則を扱おうかなーと思ってて、「リスコフ」で検索かけてみたら目についたもので。
しかも長文で、2重にすいません。

で、何を言いたいかというと
「扱う "物品" が定まらないため、"物品" ( 及びその派生クラス ) と "ケース" の間には関連を結びません。」
ということで、抽象クラス「ケース」と「物品」の間に関連がないというところです。
ここでおっしゃっている設計もすばらしいのですが、ちょっと違う考え方もあるかなーと思いましたので、余計なお世話ながら、ちょっと書かせていただきたいと思います。

私は、これ関連しちゃってもいいんじゃないのかなーと思いました。
どちらも抽象クラスなので、それ自体はインスタンス化できませんよね。
具体的なモノ(CDとか)として物品のサブクラスを定義し、それに対応するケースのサブクラス(CDケース)を定義してはじめて使えるわけです。

で、ここで問題になってくるのはケース側ですね。
このままではまさに、おっしゃる通り「扱う "物品" が定まらない」状態になっちゃいますね。
ちょっと実装よりの話になりますが、たとえばこんな解決策はいかがでしょう。

抽象クラスケースの物品属性は(もちろん)privateですよね。
そこへのアクセサはprotectedにしておきます。(#物品を格納(in 対象物品: 物品))
で、各サブクラスがpublicなアクセサを持つわけです。(+CDを格納(in 対象CD: CD))
各サブクラスは、このpublicなアクセサの中で、スーパークラスのprotectedなアクセサを利用します。
もし必要なら、スーパークラスのアクセサを使用する前に、パラメータに何かの検査をすることもできるでしょう。
とすると、万能物品ケースは、抽象クラスにあまり手を加えない「そのまんま具象化クラス」みたいなかんじですね。

ここで「ちょっと実装よりの話」と言ったとおり、実際にどういうふうに「扱う "物品" が定まらない」状態を解決するのかというのは、実装の話であって、設計のときにはあまり気にする必要はなさそうですね。
なんとなく解決しそうですし。

ということで、それはなんとかなると思ってしまえば、どうでしょう。
これはこれで、すっきりするんじゃないでしょうか。
抽象クラスケースとツリーの間に関連がひかれると、各具象クラスも同じ関連を持つようになりますね。
抽象クラスと具象クラスの関係もミラーっぽくなります。
図でいくと、左右だけでなく上下もミラーになるわけです。
上下の方は、下の方にたくさんの写像をもちますけどね。
こうすると、上下ミラーの上方は、メタ情報であるとも言えると思いませんか?
ケースと物品の関係というメタ情報に対して、CDケースとCDという具体的な情報がのっかっていくみたいな。
これ、ある意味、ちっちゃーいフレームワークになってると思うんです。

2007.07.03 12:06 URL | craftsman #LaajTvec [ 編集 ]


craftsmanさん、初めまして!
コメントありがとうございます^^

> ちょっと古い記事へのコメントですいません。
> ちょうど、私のブログでも近々リスコフの置換原則を扱おうかなーと思ってて、「リスコフ」で検索かけてみたら目についたもので。
> しかも長文で、2重にすいません。

とんでもないです、2重に嬉しいです^^


> そこへのアクセサはprotectedにしておきます。(#物品を格納(in 対象物品: 物品))
> で、各サブクラスがpublicなアクセサを持つわけです。(+CDを格納(in 対象CD: CD))
> 各サブクラスは、このpublicなアクセサの中で、スーパークラスのprotectedなアクセサを利用します。
> もし必要なら、スーパークラスのアクセサを使用する前に、パラメータに何かの検査をすることもできるでしょう。
> とすると、万能物品ケースは、抽象クラスにあまり手を加えない「そのまんま具象化クラス」みたいなかんじですね。

私も ( ジェネリックを使わない場合の ) 実装方法に関してはcraftsmanさんと同様に、ケースにprotectedなメソッドを用意するという考え方です。
恐らく、この事がコード 1-1 及び直前の文章にしか記述されておらず、クラス図にはpublicなメンバしか記述してなかった事が原因で、craftsmanさんに誤解させてしまったのだと思います、ごめんなさい。。。
(あ、それとも、コード 1-1で"物品の格納を補助"というメソッド名を採用していることを問題視しているということでしょうか?)


で、ケースと物品の間の関連についてですが・・・
ケース自体は、物品を扱うような操作をpublicなメンバとしては一切持っていませんが、内部的には備えている以上、ケースと物品の間にも関連が存在していると言えるのかもしれません。
ただ、この辺が難しいところで、意味論的には関連を持たないと言える、と私は考えました。
あくまでも補助的な位置づけであり、派生クラスがpublicなメソッドを用意することで初めて機能するからです。
これを反映させたのが私の書いたクラス図ですが、正確なクラス図を記述をするとなれば、ケースと物品の間に関連を引くべきなのかもしれません。

まぁ、ジェネリックを利用すれば、名実共に「ケースと物品は関連を持たない」と言えるので、結局の所「ジェネリックを利用する」というのが一番の解決策だろうと思います。
ジェネリックやテンプレートといった機能がない言語だと、仕方ありませんが^^;
( 最近知ったのですが、クラス図でもちゃんとジェネリックやテンプレートのパラメータの表記方法が用意されているんですね。 )


> 抽象クラスと具象クラスの関係もミラーっぽくなります。
> 図でいくと、左右だけでなく上下もミラーになるわけです。
> 上下の方は、下の方にたくさんの写像をもちますけどね。

すみません、ちょっとわからなかったんですが、上下というのは「CDケースやペンケースから物品に対して関連が引かれ、CDやペンからケースに対して関連が引かれること」に対しておっしゃっているのでしょうか?

2007.07.04 00:26 URL | よこけん #Ay6tTHf6 [ 編集 ]

なるほどなるほど
前回のコメントで、ご挨拶を忘れてましたね。
失礼しました。
あらためまして、はじめましてよこけんさん。


あ、それと最初に
私はC#使いではないので、実装に近いところでは多少意識のズレがあるかもしれません。
とお断りしておくつもりだったのを忘れてました。
すいません。私、主にJava使いです。


で、ケースと物品の意味的な関連ですが、私はあると考えちゃいました。
(逆参照しない限り)物品はケースに依存しませんが、ケースは物品に依存すると。
ケースは物品を入れるからこそケース。
たとえばCDケースはCDを入れる以外に使い道はないだろうと。

でも、よこけんさんの考えもわかります。
このへんは、たまたまとらえ方が違ったというだけのことだと思います。


上下のミラーの話ですが、説明不足ですいません。
基本クラスと具象クラスの間にどういった関連が引かれるかということよりも、どちらかというとレイヤーの話になるのかな。

私のとらえ方の場合だと、ケースが物品を包含するということは、重要な事実だということになります。
ケースが物品を包含しさえすれば、これを明確に設計に含めることができますよね。
前回「メタ情報」という言葉を使いましたが、この言葉を再び使えば、ケースが物品を包含するという事実を、メタ情報として定義するというわけです。
単にこれらの基本クラスを継承するだけで、難しいことを考えなくても自然とケースと物品の関係に従うことができる。
あたりまえのテクニックですが、これをあえて使うことで、このことをメタ情報として定義し、強制することができるわけです。

さて、ケースがメタ情報に含まれ、ケースが物品に依存するとなると、物品もメタ情報にあるべきだということになりますよね。
こうして、ケースと物品は、これらを継承する具象クラスとは明らかに違う、上位のレイヤーに含まれることになります。
メタ情報レイヤーです。
下位レイヤーにある具象クラスは全て、上位レイヤーのケースと物品の関係を反映しています。
ケースの全てのサブクラスは、なんらかの物品を包含しているわけです。
このことについて、「上下のミラー」という言い方をしてみました。
ですので、図中に関連線として表れるようなものではありません。
強いて描くとすれば、基本クラスと具象クラスたちの間に1本境界線を引いて、基本クラス側に「メタ情報」とでも書いたノートでも貼っておくくらいかと思います。
わかりにくくてすいません。
それもこれも、私はケースと物品に関連があると考えるからですね。


結局のところ、全てはケースと物品の関係についてのとらえ方の違いだったわけですね。
どちらのとらえ方が正しいかといえば、これはもう場合によりけりですから、どちらもありかと。
分析の結果は設計に大きく影響するというおもしろい例が見られました。
利害関係のない複数の人間が、一つのことを別々に分析/設計して、意見交換をするなんていう機会はなかなかありませんから。
ありがとうございました。

2007.07.04 18:20 URL | craftsman #LaajTvec [ 編集 ]


craftsmanさん、こんばんわ
コメントありがとうございます^^


私も、なんとなくですがcraftsmanさんのおっしゃっていることが理解できた気がします。
抽象クラスをメタデータという位置付けに置いた時、メタデータという位置付けの関連が存在するということですよね。
イメージ的には「抽象関連」みたいな。
・・・違うかな?(汗)
どちらにしろ、craftsmanさんのおっしゃる通り、捉え方の違いでしょうね。
私が「存在しない」と考える「ケースと物品の関連」と、
craftsmanさんが「存在する」と考える「ケースと物品の関連」は、性質の異なるものなんでしょう。


ところで、今気付いたんですが、よくよく考えたらこんな変な線(↓)引かれないですね・・・^^;いや、今となってはどうでもいいんですがw

> 「CDケースやペンケースから物品に対して関連が引かれ、CDやペンからケースに対して関連が引かれること」

2007.07.06 01:09 URL | よこけん #Ay6tTHf6 [ 編集 ]

思い出しました
こんにちは。よこけんさん

> 抽象クラスをメタデータという位置付けに置いた時、メタデータという位置付けの関連が存在するということですよね。
> イメージ的には「抽象関連」みたいな。
イメージ的には、まさにそのとおりだと思います。
私、なんかそのテの抽象を重視するタチなんですね。

> ところで、今気付いたんですが、よくよく考えたらこんな変な線(↓)引かれないですね・・・^^;
ははは。これはご愛嬌ですね。

ところで、前のコメントを書いた後、自分が書いたものに対して、何か記憶に引っかかるものがあるなと思ってたんですね。
で、思い出しましたので、ご報告しておきます。

私が書いたやり方は、マーチン・ファウラーの「アナリシスパターン」という本に出てくる「知識レベル」という話にそっくりでした。

もしこの本をお持ちだったら、第2章 責任関係あたりを読み返してみていただけると、今回のハナシがファウラーの言う「知識レベル」という言葉と絡んでおもしろいかもしれませんね。

もしお持ちでない場合は、この本オススメですよ。

2007.07.07 13:11 URL | craftsman #LaajTvec [ 編集 ]


craftsmanさん、こんばんわ^^

マーチン・ファウラーのアナリシスパターンですか、読んだ事ないです。
でも、僕が今度買おうと思っている「エンタープライズ アプリケーションアーキテクチャパターン」と同じ著者ですね!(craftsmanさんはこちらも読まれていそうですね)
「エンタープライズ アプリケーションアーキテクチャパターン」の次に読んでみようと思います、教えて頂きありがとうございました^^


アナリシスパターン―再利用可能なオブジェクトモデル
http://www.amazon.co.jp/dp/4894716933

エンタープライズ アプリケーションアーキテクチャパターン
http://www.amazon.co.jp/dp/4798105538

2007.07.08 00:54 URL | よこけん #Ay6tTHf6 [ 編集 ]












トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/130-fccf6938

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。