C#と諸々

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

--/--/-- --:--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
タグ:
トラックバック(-) | コメント(-) | このエントリーを含むはてなブックマーク
2009/01/09 01:53
途中までしか追ってないけど

interfaceに物申す
interfaceに物申す(2)

売値やら原価なんてのは青果店や問屋が勝手にりんごに貼った値札のようなもの。「原価は客には見えるべきでない」というリアリティを追及するなら、そもそも売値やら原価をりんごに持たせるのが間違いな気がする。
この場合、原価を (りんごと関連付けて) 青果店が隠し持っていればいいだけの話。
少なくとも「原価を見せたくない」ということの表現は、アクセス修飾子ではなく、よりリアルな設計で表現できる。たぶん。
2008/09/24 21:50
C#と諸々 処理のクラス化 で紹介した HogeOperation<TResult> クラスは、ややトリッキーだったかもしれない。HogeOperation<TResult> クラスを用意せず、HogeOperation クラスを直接継承して、戻り値を持つ処理をクラス化するのも一つの手だ。
方法は簡単で、HogeOperation<TResult> クラスが行っていた戻り値をフィールドに保持する処理を、各処理クラスが直接行えば良いだけ。

GetFugasOperation クラス
internal sealed class GetFugasOperation : HogeOperation
{
    public GetFugasOperation(HogeService service)
        : base(service)
    {
    }

    private IList<Fuga> _result;

    public IList<Fuga> Execute()
    {
        this.Processing();
        return this._result;
    }

    protected override void MainProcessing()
    {
        this._result = this._service.GetFugas();
    }
}

GetFugaOperation クラス
internal sealed class GetFugaOperation : HogeOperation
{
    public GetFugaOperation(HogeService service)
        : base(service)
    {
    }

    private int _id;
    private Fuga _result;

    public Fuga Execute(int id)
    {
        this._id = id;
        this.Processing();
        return this._result;
    }

    protected override void MainProcessing()
    {
        var findFugaById =
            from fuga in this._service.GetFugas()
            where (fuga.Id == this._id)
            select fuga;

        this._result = findFugaById.Single();
    }
}


どうせ引数は各処理クラスでフィールド化しなきゃいけないのだから、戻り値も各処理クラスでフィールド化した方が自然な気がする。
2008/09/23 23:08

とても似ているメソッドの共通化

ロギングのような別の関心事に関する共通処理ならばアスペクト指向という手もある。
が、ここではそういうケースではなく、あくまでも同一の関心事であるケースについて考えてみようと思う。
同一の関心事であるケースとは、コアロジックに関係する事前処理・事後処理のことである。

基本的には、リンク先に書かれている方法で充分だ。しかし、各処理が複雑な場合、一つのクラスに大量のプライベートメソッドが出てくる。
これは共通の事前処理・事後処理があるかどうかは無関係な話だが、そうすると各処理をクラス化した方がよくなるわけだ。

各処理クラスは、共通の基本クラスから派生する。
そして事前処理・事後処理は基本クラスで実装し、テンプレートメソッドパターンを適用することで、派生クラスにはコアロジックの実装に専念させる。

引数、戻り値、例外の扱い方に色々な方法があるが、僕がお勧めする方法を例を交えて 1 つだけ紹介しようと思う。

まず、次のような 2 つのクラスがあるとする。(メソッドの中身は省略)

HogeService クラス
public class HogeService
{
    public void Open();
    public void Close();

    public void AddFuga(Fuga target);
    public IList<Fuga> GetFugas();
}

Fuga クラス
public class Fuga
{
    public int Id { get; }
}

HogeService クラスの AddFuga(Fuga target) や GetFugas() を使用する場合、次の制約がある。
  • 事前処理として、Open を呼び出さなければならない
  • 事後処理として、Close を呼び出さなければならない
このクラスを若干使いづらいと感じたので、委譲によるアダプターパターンを適用した、Hoge というラッパークラスを作ることにした。
Hoge クラスでは、Open・Close を隠蔽し、また ID を元に単一の Fuga を取得するメソッドを新たに提供することにした。

Hoge クラス
public class Hoge
{
    public void AddFuga(Fuga target)
    {
        HogeService service = new HogeService();
        service.Open();
        try
        {
            service.AddFuga(target);
        }
        finally
        {
            service.Close();
        }
    }
    public IList<Fuga> GetFugas()
    {
        HogeService service = new HogeService();
        service.Open();
        try
        {
            return service.GetFugas();
        }
        finally
        {
            service.Close();
        }
    }
    public Fuga GetFuga(int id)
    {
        HogeService service = new HogeService();
        service.Open();
        try
        {
            var findFugaById =
                from fuga in service.GetFugas()
                where (fuga.Id == id)
                select fuga;
            return findFugaById.Single();
        }
        finally
        {
            service.Close();
        }
    }
}

見ての通り、各処理は try ブロック内が異なるだけで他は全て同一の処理を行っている。

ではここで、各処理をクラス化してみよう。
まず、基本クラスである HogeOperation クラス。

HogeOperation クラス
internal abstract class HogeOperation
{
    public HogeOperation(HogeService service)
    {
        this._service = service;
    }

    protected readonly HogeService _service;

    protected void Processing()
    {
        this._service.Open();
        try
        {
            this.MainProcessing();
        }
        finally
        {
            this._service.Close();
        }
    }

    protected abstract void MainProcessing();
}

戻り値を持たない処理は、この HogeOperation を直接継承する。
また、外部から処理を実行できるよう、適切なパラメータを持つ Execute メソッドを用意する。受け取った引数は、MainProcessing メソッドで使用できるよう、フィールドに保持しておく。

AddFugaOperation クラス
internal sealed class AddFugaOperation : HogeOperation
{
    public AddFugaOperation(HogeService service)
        : base(service)
    {
    }

    private Fuga _target;

    public void Execute(Fuga target)
    {
        this._target = target;
        return this.Processing();
    }

    protected override void MainProcessing()
    {
        this._service.AddFuga(this._target);
    }
}

続いて、戻り値を持つ処理の基本クラスとなる、HogeOperation<TResult> クラス。
このクラスは HogeOperation クラスを継承するのだが、new 修飾子を使用して、Processing メソッドが戻り値を返すように再定義を行っている。MainProcessing メソッドも戻り値を返せるようにする必要があるが、抽象メソッドなので new 修飾子による再定義はできない。代わりに、out パラメータとして戻り値を返せるバージョンの MainProcessing メソッドを新たに定義している。

HogeOperation<TResult> クラス
internal abstract class HogeOperation<TResult> : HogeOperation
{
    public HogeOperation(HogeService service)
        : base(service)
    {
    }

    private TResult _result;

    protected new TResult Processing()
    {
        base.Processing();
        return this._result;
    }

    protected override void MainProcessing()
    {
        this.MainProcessing(out this._result);
    }

    protected abstract void MainProcessing(out TResult result);
}

戻り値を持つ処理は、この HogeOperation<TResult> クラスを継承する。
こちらも、外部から処理を実行できるよう、適切なパラメータを持つ Execute メソッドを用意する。

GetFugasOperation クラス
internal sealed class GetFugasOperation : HogeOperation<IList<Fuga>>
{
    public GetFugasOperation(HogeService service)
        : base(service)
    {
    }

    public IList<Fuga> Execute()
    {
        return this.Processing();
    }

    protected override void MainProcessing(out IList<Fuga> result)
    {
        result = this._service.GetFugas();
    }
}

GetFugaOperation クラス
internal sealed class GetFugaOperation : HogeOperation<Fuga>
{
    public GetFugaOperation(HogeService service)
        : base(service)
    {
    }

    private int _id;

    public Fuga Execute(int id)
    {
        this._id = id;
        return this.Processing();
    }

    protected override void MainProcessing(out Fuga result)
    {
        var findFugaById =
            from fuga in this._service.GetFugas()
            where (fuga.Id == this._id)
            select fuga;

        result = findFugaById.Single();
    }
}

これらの処理クラスを使用することで、Hoge クラスは次のようにシンプルなコードとなる。

Hoge クラス
public class Hoge
{
    public void AddFuga(Fuga target)
    {
        HogeService service = new HogeService();
        new AddFugaOperaion(service).Execute(target);
    }
    public IList<Fuga> GetFugas()
    {
        HogeService service = new HogeService();
        return new GetFugasOperaion(service).Execute();
    }
    public Fuga GetFuga(int id)
    {
        HogeService service = new HogeService();
        return new GetFugaOperaion(service).Execute(id);
    }
}

以上で処理のクラス化は完了となる。
// 追記 (2008/09/24)
HogeOperation<TResult> クラスは、ややトリッキーだったかもしれない。別解を記事にしたのでこちらも参照して頂きたい。
戻り値を持つ処理のクラス化の別解
// 追記ここまで


さて、最初にも書いたが、各処理が複雑でない内は、デリゲートの注入で充分だろう。この例では各処理が複雑なものではないため、各処理をクラス化したことによって逆に複雑になってしまっている。だが、処理が複雑化してきたときには、処理をクラス化することは非常に有効である。これは単一責任の原則を適用するということだ。例えば Hoge クラスに何らかの状態を持たせたりする場合、状態操作の処理が Hoge クラス内で行われる。各処理で行われる複雑な処理は、処理クラスに切り離されているため、Hoge クラスが責任を持つべき処理だけが Hoge クラス内に残るわけである。


ところで、HogeOperation クラス、HogeOperation<TResult> クラスには、protected な Processing メソッドを用意して、外部から処理を実行させるための Execute メソッドは実際の処理クラスに用意しているが、なぜわざわざこのようなことをしているのか。
例えば、引数はコンストラクタなり引数設定用のメソッドなりで設定できるようにしてしまえば、Processing メソッドの代わりに public な Execute メソッドを基本クラスに用意できるだろう。
しかし、そうすると各処理がスローする可能性のある例外を明示する時、つまり、XML コメントの再定義で困る。結局 XML コメントを再定義するためには Execute メソッドをオーバーライドするなり new 修飾子で再定義するなりしなければならない。ならば、初めから Execute メソッドは処理クラスで定義しなければならないようにした方が良いと考えたわけだ。
無論、XML コメントの再定義を行わないつもりならば、引数はコンストラクタなり引数設定用のメソッドなりで設定できるようにして、基本クラスで Execute メソッドを定義してしまっても良い。
引数設定用のメソッドを用意するのなら、フルエントインターフェイスを意識すると良い。
当初はこういった別の実装方法も取り上げるつもりだったのだが、収拾がつかなくなってしまったため省略した。
2007/10/19 22:12

Martin Fowler's Bliki in Japanese - ドメインモデル貧血症

有名なアンチパターンです。僕も昔やってしまったことがあります。

例えば、図書の貸出システムを作るとします。このシステムは利用者が直接操作し、借り入れ手続きや返却手続きを行います。

ドメインモデルを適切に適用した設計では、「書籍」クラスや「利用者」クラスを用意し、「利用者」クラスに「借りる」メソッドや「返すメソッド」を用意するでしょう。

public sealed class 書籍
{
    public int 管理番号 { get { ... } }
    public string タイトル { get { ... } }
    public string 著者 { get { ... } }
}

public sealed class 利用者
{
    public int 利用者ID { get { ... } }
    public ReadOnlyCollection<書籍> 借りてる書籍 { get { ... } }
    public void 借りる(書籍 対象書籍) { ... }
    public void 返す(書籍 対象書籍) { ... }
}


しかし、ドメインモデル貧血症に陥っている設計では、「利用者」クラスに「借りる」メソッドや「返す」メソッドがありません。代わりに、「借り入れ操作」クラスといった感じの、ロジックのみのクラスが存在します。

public sealed class 利用者
{
    public int 利用者ID { get { ... } }
    public ReadOnlyCollection<書籍> 借りてる書籍 { get { ... } }
}

public sealed class  借り入れ操作
{
    public void 借りる(利用者 対象者, 書籍 対象書籍) { ... }
    public void 返す(利用者 対象者, 書籍 対象書籍) { ... }
}


ドメインモデル貧血症では、データと処理が分離されてしまいます。これは決してオブジェクト指向ではありません。
また、この例では、「利用者」が「借りてる書籍」を、読み取り専用コレクションのプロパティとして公開しています。「借り入れ操作」クラスは、「借りてる書籍」をどうやって変更するのでしょうか。カプセル化を崩すか、プロパティの型を読み取り専用でなくするか、あるいは利用者クラスに補助的なメソッドを用意するか。どうするにしろ、複雑さが増してしまうかと思います。


オブジェクト指向じゃないことが悪いのではありません。オブジェクト指向を適用しているつもりで貧血症を起こしているからダメなのです。
2007/08/28 17:26

NAgilerの日記 - Agile Principles, Patterns, and Practices in C# (^o^)


なんと、書籍「アジャイルソフトウェア開発の奥義」の C# 版だそうです
ほ、ほしぃ・・・。でも洋書読めない  ;-;
邦訳版が出てくれることを祈ります ( できれば、アジャイルソフトウェア開発の奥義と同じ人の翻訳で ^^ )

# よく見たらけっこう前 ( 2006年8月 ) に出てたんですね、知らなかった ^^;
2007/08/13 23:23
リスコフの置換原則 ( 以降、LSP ) は非常に興味深い。僕がオブジェクト指向の原則を学ぶきっかけとなったのが LSP だからかもしれない。

例外について見てみる。基本クラスでスローするとされていない例外は、LSP により、派生クラスでもスローしてはいけないことになる。
ここで言う "基本クラスでスローするとされていない例外" とは "基本クラスでスローしない例外" ではない。.NET なら XML コメントの <exception> セクションで宣言されていない例外のことを指している。Java は詳しくないけど Java なら throws で宣言されていない例外。 ( ※ )
これを逆に言えば、基本クラスではスローしなくとも、派生クラスでスローされる可能性があるのなら、基本クラスで宣言しなければならないということになる。でなければ LSP に違反してしまう。

ここで注意しなければいけないのが、例外の抽象度だと思う。
例えば、書籍データをデータストアから取得するための GetBookData メソッドが IBookFinderインターフェイスに定義されているとする。
IBookFinder インターフェイスを利用するアプリケーションは今の所 2 つあるとする。片方はデータストアとして XML ファイルを利用し、BookFinderFromXml クラスが IBookFinder インターフェイスを実装している。もう片方はデータストアとして DB を利用し、BookFinderFromDB クラスが IBookFinder インターフェイスを実装している。

BookFinderFromXml クラスの GetBookData メソッドは、XML ファイルが見つからなかった時に FileNotFoundException をスローする。
一方、BookFinderFromDB クラスの GetBookData メソッドは、DB が見つからなかった時に DBNotFoundException をスローする ( こんな例外 .NET に用意されていないけど )。

では、IBookFinder インターフェイスの GetBookData メソッドにはこれらの例外がスローされる可能性があると宣言するのかというと、それは違う。そんなことしてたら、派生クラスが増えるたびに、IBookFinder インターフェイスに修正をすることになるかもしれない。
ここで抽象度が出てくる。例えば DataStoreNotFoundException といった例外クラスを定義し、IBookFinder インターフェイスの GetBookData メソッドでは、この例外をスローすると宣言するべきである。派生クラスでは、FileNotFoundException や DBNotFoundException の代わりに、この例外、またはこの例外を派生させた例外 ( DataStoreFileNotFoundException とか ) をスローできる。


ちなみに、LSP に準拠させる前の BookFinderFromXml クラスは FileNotFoundException をスローしていたが、FileNotFoundException のこういう使い方は不適切な気がする。LSP とか関係なく、FileNotFoundException ではなく「データストアが存在しない」という意味の例外をスローすべきだと思う。
LSP を適用することで、この点も解消されている所が面白い。

なお、この例で挙げた設計は、僕がこの記事を書くために適当に考えただけの設計であり、実際の業務アプリには適用できない ( または適用すべきでない ) 設計かもしれない。クラス名も適当だし。あくまでも LSP と例外について書きたかっただけなので。



※ .NET と Java では例外の扱い方に大きな差があります。ここでは .NET の <exception> セクションと Java の throws を並べてますが、全く性質の異なるものです。
2007/06/03 03:17
先日の記事 リスコフの置換原則とミラー階層 で、ミラー階層の基本クラスを両方ともジェネリック型にすると、ジェネリックの型制約の指定がうまくできない、といった事を書きました。
あの後、実は指定できないこともないということに気づきました。ごめんなさい。

まず、なぜうまく指定できないと書いたかですが、先日の記事であげたケースと物品の例で説明します。

ケース クラスには格納する物品を指定するためのジェネリックパラメータを用意します。
物品 クラスには親となるケースを指定するためのジェネリックパラメータを用意します。
つまり、ケース<TArticle> クラスと 物品<TCase> クラス となるわけです。
この時、ケース クラスのジェネリックパラメータ TArticle の型制約には、物品<TCase> を指定するわけですが、物品<TCase> の型パラメータ TCase を指定しなければなりません。ここで不都合が生じるわけです。TCase に対して ケース<TArticle> と指定することはできますが、これではダメなんです。
なぜ不都合かというと、ジェネリック クラスは、型パラメータが完全一致していない場合、変換不可だからです。例えば、List<object> と List<int> では、object と int の間には継承関係がありますが、そんなの関係なく変換不可です。List<int> 型の変数を List<object> 型にキャストすることはできないのです。
すると、TCase が指定できないことがわかります。例えば、ケース クラスを継承した CDケース クラスでは、TCase は、CDケース にならなければなりません。ケース<CD> ではダメなのです。 ( 物品<ケース<CD> は 物品<ケース> への変換が不可ということです。 )

と、ちょっとややこしい話になってしまいましたが、要はこのままでは型制約の指定は無理だということです。で、先日の記事には無理だよ~と書いたわけです。


では、どうすればいいか、というのが今回の記事の本題です。
答えは、「自身のクラス階層を含め、ミラー階層において関連するクラス ( 階層 ) 全てを型パラメータに持たせる」 となります。
つまり、ケースには TCase と TArticle の2つの型パラメータを持たせ、物品にも TCase と TArticle の2つの型パラメータを持たせるわけです。
こうすることによって、先ほどの問題が解決できます。先ほど指定できなかった TCase には、ケースに新しく追加した型パラメータ TCase を指定すればいいわけですから。

コードにすると以下のようになります。

public abstract class ケース<TCase, TArticle>
    where TCase : ケース<TCase, TArticle>
    where TArticle : 物品<TCase, TArticle>
{
}

public abstract class 物品<TCase, TArticle>
    where TCase : ケース<TCase, TArticle>
    where TArticle : 物品<TCase, TArticle>
{
}


これらを継承した CDケース クラスと CD クラスは以下のようになります。

public sealed class CDケース : ケース<CDケース, CD>
{
}

public sealed class CD : 物品<CDケース, CD>
{
}


TCase と TArticle に関連しない型を指定した場合 ( 例えば ケース<CDケース, ペン> ) 、コンパイルエラーとなりますので、変なバグが入り込んだりすることはないかと思います。
これで問題は解決しましたが、一つ気を付けなければならないことがあります。ケース クラスに TCase という型パラメータ、そして 物品 クラスに TArticle という型パラメータがあることは、他人から見ておかしなことをやっているように見えてしまう ( つまり理解し難い ) ということです。こればかりは仕方ありませんので、理由や使い方をしっかりとドキュメントに書き記すようにしましょう。( いや、理由はしっかり書くとややこしくなるからざっくりとの方がいいかもw )


[ 余談 ]
ちなみに、これ以外にも方法はあります。非ジェネリックな基本クラスを用意するという方法です。つまり、「ケース クラス」 とこれを継承した 「ケース<TArticle> クラス」、「物品 クラス」 とこれを継承した 「物品<TCase> クラス」 を用意するわけです。こうすれば、型制約には非ジェネリックな基本クラスを指定することができます。

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. "物品ケース" を用意しない場合



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


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


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