21

私は会社の内部プロジェクトに取り組んでおり、プロジェクトの一部は、XML ファイルからさまざまな「タスク」を解析して、後で実行するタスクのコレクションにできるようにすることです。

Task の各タイプには多数の異なる関連フィールドがあるため、Task の各タイプを個別のクラスで表すのが最善であると判断しました。

これを行うために、抽象基本クラスを構築しました。

public abstract class Task
{
    public enum TaskType
    {
        // Types of Tasks
    }   

    public abstract TaskType Type
    {
        get;
    }   

    public abstract LoadFromXml(XmlElement task);
    public abstract XmlElement CreateXml(XmlDocument currentDoc);
}

各タスクは、この基本クラスから継承され、渡された XmlElement から自分自身を作成し​​、自分自身をシリアル化して XmlElement に戻すために必要なコードを含んでいました。

基本的な例:

public class MergeTask : Task
{

    public override TaskType Type
    {
        get { return TaskType.Merge; }
    }   

    // Lots of Properties / Methods for this Task

    public MergeTask (XmlElement elem)
    {
        this.LoadFromXml(elem);
    }

    public override LoadFromXml(XmlElement task)
    {
        // Populates this Task from the Xml.
    }

    public override XmlElement CreateXml(XmlDocument currentDoc)
    {
        // Serializes this class back to xml.
    }
}

パーサーは、次のようなコードを使用してタスク コレクションを作成します。

XmlNode taskNode = parent.SelectNode("tasks");

TaskFactory tf = new TaskFactory();

foreach (XmlNode task in taskNode.ChildNodes)
{
    // Since XmlComments etc will show up
    if (task is XmlElement)
    {
        tasks.Add(tf.CreateTask(task as XmlElement));
    }
}

これらはすべてうまく機能し、タスクごとに個別のクラスを持つ構造を維持しながら、基本クラスを使用してタスクを渡すことができます。

ただし、TaskFactory.CreateTask のコードには満足できません。このメソッドは XmlElement を受け取り、適切な Task クラスのインスタンスを返します。

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    {
        switch(elem.Name)
        {
            case "merge":
                return new MergeTask(elem);
            default:
                throw new ArgumentException("Invalid Task");
        }
    }
}

XMLElement を解析する必要があるため、インスタンス化する子クラスを選択するために巨大な (実際のコードでは 10 ~ 15 ケース) スイッチを使用しています。このメソッドをクリーンアップするために、ここで実行できる何らかのポリモーフィック トリックがあることを願っています。

何かアドバイス?

4

10 に答える 10

12

これを行うためにリフレクションを使用します。余分なコードを追加する必要なく、基本的に拡張するファクトリを作成できます。

「System.Reflection を使用」していることを確認し、インスタンス化メソッドに次のコードを配置します。

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    { 
        try
        {
          Assembly a = typeof(Task).Assembly
          string type = string.Format("{0}.{1}Task",typeof(Task).Namespace,elem.Name);

          //this is only here, so that if that type doesn't exist, this method
          //throws an exception
          Type t = a.GetType(type, true, true);

          return a.CreateInstance(type, true) as Task;
        }
        catch(System.Exception)
        {
          throw new ArgumentException("Invalid Task");
        }
    }
}

もう 1 つの観察結果は、このメソッドを静的にして Task クラスから切り離すことができるため、TaskFactory を新たに作成する必要がなく、維持する必要のある可動部分を自分自身で節約できるということです。

于 2008-08-26T02:45:00.907 に答える
6

各クラスの「プロトタイプ」インスタンスを作成し、ファクトリ内のハッシュテーブルに配置します。XML で期待される文字列をキーとして使用します。

そのため、CreateTask は、ハッシュテーブルから get() することによって、適切な Prototype オブジェクトを見つけるだけです。

次に、LoadFromXML を呼び出します。

クラスをハッシュテーブルに事前にロードする必要があります。

もっと自動化したいなら…

ファクトリで静的登録メソッドを呼び出すことにより、クラスを「自己登録」することができます。

Task サブクラスの静的ブロックに (コンストラクターを使用して) 登録する呼び出しを配置し​​ます。次に、静的ブロックを実行するクラスを「言及」するだけです。

Task サブクラスの静的配列は、それらを「言及」するのに十分です。または、リフレクションを使用してクラスに言及します。

于 2008-08-26T05:00:21.753 に答える
4

依存性注入についてどう思いますか? 私は Ninject を使用しており、その中のコンテキスト バインディング サポートはこの状況に最適です。要求されたときに IControllerFactory を使用してコントローラーを作成し、コンテキスト バインディングを使用する方法については、このブログ投稿を参照してください。これは、状況に合わせて使用​​する方法に関する優れたリソースになるはずです。

于 2008-08-26T03:19:15.677 に答える
2

@Tim、私はあなたのアプローチとChanChansの簡略化されたバージョンを使用することになりました。コードは次のとおりです。

public class TaskFactory
    {
        private Dictionary<String, Type> _taskTypes = new Dictionary<String, Type>();

        public TaskFactory()
        {
            // Preload the Task Types into a dictionary so we can look them up later
            foreach (Type type in typeof(TaskFactory).Assembly.GetTypes())
            {
                if (type.IsSubclassOf(typeof(CCTask)))
                {
                    _taskTypes[type.Name.ToLower()] = type;
                }
            }
        }

        public CCTask CreateTask(XmlElement task)
        {
            if (task != null)
            {
                string taskName = task.Name;
                taskName =  taskName.ToLower() + "task";

                // If the Type information is in our Dictionary, instantiate a new instance of that task
                Type taskType;
                if (_taskTypes.TryGetValue(taskName, out taskType))
                {
                    return (CCTask)Activator.CreateInstance(taskType, task);
                }
                else
                {
                    throw new ArgumentException("Unrecognized Task:" + task.Name);
                }                               
            }
            else
            {
                return null;
            }
        }
    }
于 2008-08-26T17:17:05.040 に答える
2

@jholland

Type 列挙型は必要ないと思います。いつでも次のようなことができるからです。

列挙型?

ハッキーに感じることは認めます。リフレクションは最初は汚いと感じますが、獣を飼いならすと、それができることを楽しむことができます. (再帰を覚えておいてください。汚い感じですが、良いです)

トリックは、メタデータ (この場合は xml から提供される文字列) を分析し、それを実行時の動作に変換していることを認識することです。それが反射が最も得意とするところです。

ところで: is 演算子はリフレクションでもあります。

http://en.wikipedia.org/wiki/Reflection_(computer_science)#Uses

于 2008-08-26T03:04:51.920 に答える
1

@ちゃんちゃん

私はリフレクションのアイデアが好きですが、同時にリフレクションを使うのはいつも恥ずかしがり屋でした。より簡単なはずのものを回避するのは、常に「ハック」のように思えます。私はそのアプローチを検討しましたが、コードの匂いが同じ量であれば、switch ステートメントの方が高速であると考えました。

あなたは私に考えさせました、私はいつでも次のようなことができるので、 Type 列挙型は必要ないと思います:

if (CurrentTask is MergeTask)
{
    // Do Something Specific to MergeTask
}

GoF Design Patterns の本をもう一度開く必要があるかもしれませんが、適切なクラスをポリモーフィックにインスタンス化する方法があると本当に思っていました。

于 2008-08-26T02:56:37.057 に答える
1

列挙型?

抽象クラスで Type プロパティと列挙型を参照していました。

反省です!他の誰かが検討する時間を与えるために、約 30 分で回答を承認済みとしてマークします。それは楽しいトピックです。

于 2008-08-26T03:08:44.487 に答える
1

開いたままにしてくれてありがとう、私は文句を言いません。これは楽しいトピックです。ポリモーフィックにインスタンス化できるといいのですが。
Ruby (およびその優れたメタプログラミング) でさえ、これにはリフレクション メカニズムを使用する必要があります。

于 2008-08-26T03:12:34.697 に答える
1

@デール

私は nInject を詳細に調べていませんが、依存性注入に関する私の高レベルの理解から、ChanChans の提案と同じことを達成すると信じています。

ここで必要な 1 回限りの状況では、追加のライブラリをリンクして 1 か所だけ呼び出すよりも、ハンドロールされたリフレクション コードを使用する方が良い方法だと思います...

しかし、nInject がここで私に与える利点を理解していないかもしれません。

于 2008-08-26T03:38:21.980 に答える
1

一部のフレームワークは、必要に応じてリフレクションに依存する場合がありますが、ほとんどの場合、必要に応じてブートストラップを使用して、オブジェクトのインスタンスが必要なときに何をすべきかを設定します。これは通常、汎用ディクショナリに格納されます。Ninjectを使い始めた最近まで、私は自分のものを使っていました。

Ninject で私が気に入った主な点は、リフレクションを使用する必要がある場合に必要ないことです。代わりに、信じられないほど高速な .NET のコード生成機能を利用します。使用しているコンテキストでリフレクションがより高速になると思われる場合は、そのように設定することもできます。

現時点であなたが必要としているものに対して、これはやり過ぎかもしれませんが、依存性注入を指摘し、将来のために考える材料を提供したかっただけです. レッスンのために道場を訪問してください。

于 2008-08-26T03:48:50.740 に答える