2006 年 7 月 9 日 22 時 33 分

実装の分割 #4: インタフェース


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


さて、昨日のコードを再掲しよう。

    LinkTargetHandler hander = null;

    switch (pscd.pwszExt.ToLower()) {

    case ".lnk":
        hander = new LinkTargetHandler(
                new ShortcutFile(pscd.wszFile).GetLinkTarget);
        break;

    case ".url":
        hander = new LinkTargetHandler(
                new InternetShortcutFile(pscd.wszFile).GetInternetShortcutTarget);
        break;

    default:
        hander = new LinkTargetHandler(
                new NullShortcut().GetOtherTarget);
        break;

    }

    return hander();

クラスを利用するための初期化は個別に行い、
それを利用するタイミングをデリゲートを用いて統一した。

デリゲートのメリットは、同じシグネチャを持つメソッドを、
変数に記憶しておき、後で呼び出すことができることだ。

シグネチャさえ合っていれば、メソッドの名前や、
静的メソッド・インスタンスメソッドを問わずに
まったく同じように扱うことができるのも強みである。

デリゲートを経由で呼び出す実装側にには
メソッドのシグネチャ以上の制約を課す必要がないため、
クラスの状態変更通知などを他のクラスに送るための、
イベント・リスナーの仕組みで利用されることが多い。

ただ、デリゲート型の登録や、デリゲート変数の作成など、
その利用にある程度の手間がかかるのは事実である。

今回の要件では、switch の中でやることは決まっている。
そのため、デリゲートを使う方法は少し大掛かり過ぎるのだ。
こういった場合、デリゲートよりもシンプルな方法がある。

それは、「インタフェース」だ。

デリゲートが、メソッド 1 つに対する参照を持つならば、
インタフェースは、複数のメソッドに対する参照を持つ。

インタフェースは、クラスに対して機能し、
特定のシグネチャを持つメソッド(複数の場合もある)を
実装することを要求するのだ。

デリゲート型変数を経由してメソッドを呼び出せるのと同様、
インタフェース型変数を経由して、
インタフェースに規定されているメソッドを呼び出せる。

ただ、インタフェースはデリゲートと違い、
クラス側が、インタフェースを実装していることを、
クラス定義に明示する必要がある。

また、インタフェースは「契約」であるため、
インタフェースが規定しているメソッドを、
クラス側が「全て」実装する必要がある。

過去に出てきた IShellLinkW などもインタフェースだが、
これらは、シェル側が要求している契約である。
なお、6/27 にも IColumn インタフェースを作っている。
このときは説明もせずにさっさと作ったが、
あれも、Column を実装するための契約なのだ。

では、ショートカット用のインタフェース型を作ってみよう。
昨日作った ShortcutFile 等のクラスに共通する契約は、
「リンク先を文字列として取得できる」ということだ。
これをインタフェースとして定義するとこうなる。

========== IShortcut.cs ==========

using System;

namespace LoaferShellEx.Column {

    public interface IShortcut {

        string GetTarget();

    }

}

========== IShortcut.cs ==========

C# ではインタフェース名の先頭に I をつける規則がある。
IShortcut という名前にしよう。
つまり、Shortcut の機能を実装するという契約だ。

インタフェースの契約書は非常にシンプルであり、
クラスと同じような記述方法で、
メソッドの宣言だけを並べて記述する。

上記の場合は、GetTarget というメソッドを 1 つ定義した。
これは前回デリゲートして定義したものに似ている。

インタフェースには IShortcut という名前があるので、
GetTarget は、契約をしたクラスの実装に対して、
リンク先の文字列を返せという意味の指示となる。

ちなみに、メソッドの特別形であるプロパティも使えるが、
ここで混ぜると話がややこしくなるので、今はやめておく。

では、昨日作った 3 クラスに、
IShortcut インタフェースを適用する。
方法は全て同じなので、ShortcutFile だけ掲載しよう。

========== ShortcutFile.cs ==========

using System;
using System.Text;
using System.Runtime.InteropServices;
using LoaferShellEx.Interop;

namespace LoaferShellEx.Column {

    public class ShortcutFile : IShortcut {

        private string _path;

        public ShortcutFile(string path) {
            _path = path;
        }

        public string GetTarget() {
            // 省略。_path から読み込み string を返す。
        }

    }

}

========== end of ShortcutFile.cs ==========

見た目はほとんど変わっていない。

クラス名の後に、「: IShortcut」が付いていることで、
このクラスは IShortcut を実装していることを宣言する。

IShortcut で規定されているメソッドは GetTarget なので、
クラスでは、同じシグネチャを持つ GetTarget を実装する。
昨日の GetLinkTarget の名前を変えるだけで OK だ。

残りの InternetShortcutFile と NullShortcut も、
同じように記述を変更する。

さて、呼び出し側はどうなるか。

    IShortcut shortcut = null;

    switch (pscd.pwszExt.ToLower()) {

    case ".lnk":
        shortcut = new ShortcutFile(pscd.wszFile);
        break;

    case ".url":
        shortcut = new InternetShortcutFile(pscd.wszFile);
        break;

    default:
        shortcut = new NullShortcut();
        break;
           
    }

    return shortcut.GetTarget();

インタフェースは、扱う側は非常に楽である。

インタフェース型は、デリゲートのように、
複数のインスタンスを保持するような複雑性はない。

クラス側ではインタフェースの実装を明示しているため、
インタフェース型に暗黙の変換を行うことができる。
そのため、インスタンスを代入するだけの単純な構文で、
インタフェースへの参照を格納することができるのだ。

そのため、呼び出し側も非常にシンプルになるのである。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.