2006 年 7 月 8 日 23 時 31 分

実装の分割 #3: 初期化と利用


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


デリゲートを使うと呼び出しを集約することができた。

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

    LinkTargetHandler hander = null;

    switch (pscd.pwszExt.ToLower()) {

    case ".lnk":
        hander = new LinkTargetHandler(GetShortcutTarget);
        break;

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

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

    }

    return hander(pscd.wszFile);

デリゲートを使った場合、
まずはデリゲート型変数にメソッドを登録し、
後で変数経由でそれを呼び出した。

いわば、メソッド呼び出しを遅延し、
呼び出すタイミングを共通化することに成功したわけだ。

しかし、不満点もある。GetOtherTarget メソッドの
シグネチャを変更し、本来は不要である引数を
追加する必要があったからだ。

昨日使ったデリゲートは、戻り値に string を返し、
引数に string を取るという型であった。

    delegate string LinkTargetHandler(string path);

デリゲートは、型に対する強い制約があるため、
登録するメソッドのシグネチャを統一する必要がある。

これを回避するためにはどうすればよいか。

今回デリゲート変数経由で呼び出される処理を考えてみよう。
1. 変数が保持するメソッドに引数を渡す。
2. メソッドが内部で処理をする。
3. メソッドが返す結果を受け取る。

使っているメソッドは、全て外部に依存しない
関数型のメソッド(引数以外に依存しない)であるため、
当たり前のことだが、メソッドを呼び出す場合、
「呼び出した時点」で上記の処理を「全てその場で」行う。

呼び出し側の視点で考えれば、1 が初期化、3 が結果だ。
初期化というのは、扱うものによって作法が異なるため、
メソッド呼び出しから分離することを考える。

こういった処理は、言語によっては
クロージャを使って実現できるのだが、
C# では、軽量のクラスでラップして作る。

初期化と呼び出し(利用)を分けるということは、
初期化がコンストラクタに該当し、
利用がメンバメソッド呼び出しとなるわけだ。
そうすれば、メソッドを呼び出す時点で引数は不要となる。

では、それぞれをクラスに分けてみよう。
あくまでも、無理やりクラス化しただけなので、
メソッド内の処理は変わらない。なので省略する。

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

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

namespace LoaferShellEx.Column {

    public class ShortcutFile  {

        private string _path;

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

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

    }

}

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

========== InternetShortcutFile.cs ==========

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

namespace LoaferShellEx.Column {

    public class InternetShortcutFile {

        private string _path = null;

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

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

    }

}

========== end of InternetShortcutFile.cs ==========

========== NullShortcut.cs ==========

using System;
using LoaferShellEx.Interop;

namespace LoaferShellEx.Column {

    public class NullShortcut {

        public NullShortcut() {
        }

        public string GetOtherTarget() {
            throw new ComFalseException();
        }

    }
}

========== end of NullShortcut.cs ==========

このようにすると、GetOtherTarget に引数が要らなくなる。

では、これを利用するコードを書いてみよう。
デリゲート変数には、静的メソッド(関数等)だけでなく、
メンバメソッドへの参照も格納することができ、
後でインスタンスのメソッドを呼び出すこともできるのだ。

以前は初期化として必要だった引数は不要となり、
デリゲートの宣言は以下のようになる。

    private delegate string LinkTargetHandler();

続いて、LinkTargetColumn の GetValue メソッドだ。

    // デリゲートを用意
    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();

デリゲート変数への代入は不思議な感じであり、
インスタンスのメソッドを呼び出すのと同じ構文で、
括弧を省略することでメソッドへの参照を渡す。
デリゲート変数がインスタンスへの参照を保持するので、
new LinkTargetHandler(new NullShortcut().GetOtherTarget);
なんて書き方ができるのだ。

これで、メソッドへの余分な引数の追加はなくなった。
ただし、昨日よりさらに複雑になった(笑)
明日は、これらをもう少し見やすく書き換えよう。

# 題材があまりよくないので、
# 昨日や今日行った分割には、それほどメリットはない。
# 出来る人がみれば、お前何やってんだと思うかも。
# ここまで読んできた人はもうお分かりだと思うが、
# これらは、明日ある内容を説明するための布石なだけだ。



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