質問
私は会社の内部プロジェクトに取り組んでいます。プロジェクトの一部は、XML ファイルのさまざまな「タスク」を解析して、後で実行するタスクのコレクションにできるようにすることです。
各タイプのタスクには多数の異なる関連フィールドがあるため、各タイプのタスクを別個のクラスで表すことが最善であると判断しました。
これを行うために、抽象基本クラスを構築しました。
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 個のケース) を使用しています。このメソッドをクリーンアップするためにここで実行できる、ある種のポリモーフィックなトリックがあることを願っています。
何かアドバイス?
解決
これを行うにはリフレクションを使用します。追加のコードを追加することなく、基本的に拡張するファクトリーを作成できます。
「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 を新しく作成する必要がなく、また、保守するための移動部分を節約できることです。
他のヒント
各クラスの「プロトタイプ」インスタンスを作成し、XML で予期される文字列をキーとしてファクトリ内のハッシュテーブルに配置します。
したがって、createTaskは、ハッシュテーブルからget()ingによって正しいプロトタイプオブジェクトを見つけるだけです。
次に、それに対して LoadFromXML を呼び出します。
クラスをハッシュテーブルに事前にロードする必要があります。
もっと自動化したい場合は...
ファクトリで静的な register メソッドを呼び出すことで、クラスを「自己登録」することができます。
Task サブクラスの静的ブロックに、(コンストラクターを使用して) 登録する呼び出しを配置します。次に、静的ブロックを実行するクラスを「メンション」するだけです。
Task サブクラスの静的配列は、それらを「言及」するのに十分です。または、リフレクションを使用してクラスについて言及します。
依存関係の注入についてどう思いますか?私は Ninject を使用していますが、そのコンテキスト バインディング サポートはこの状況に最適です。これを見てください ブログ投稿 要求されたときに IControllerFactory でコントローラーを作成してコンテキスト バインディングを使用する方法について説明します。これは、状況に応じて使用する方法に関する優れたリソースとなるはずです。
@jholand
いつでも次のようなことができるので、Type enum は必要ないと思います。
列挙型?
それがハックだと感じることは認めます。Reflection は最初は汚いように感じますが、一度この獣を飼いならすと、それができることを楽しめるようになります。(再帰を思い出してください。汚いように感じますが、それは良いことです)
重要なのは、メタデータ (この場合は XML から提供された文字列) を分析し、それを実行時の動作に変換していることを認識することです。それがリフレクションの最も得意なことです。
ところで:is 演算子はリフレクションでもあります。
http://en.wikipedia.org/wiki/Reflection_(computer_science)#Uses
@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;
}
}
}
@ちゃんちゃん
私はリフレクションというアイデアが好きですが、同時にリフレクションを使用することを常に恥ずかしがっていました。もっと簡単なはずのことを回避するための「ハック」のように私はいつも思っていました。私はそのアプローチを検討しましたが、同じ量のコード臭であれば switch ステートメントの方が高速であると考えました。
確かに、次のようなことがいつでもできるので、Type enum は必要ないと思います。
if (CurrentTask is MergeTask)
{
// Do Something Specific to MergeTask
}
GoF デザイン パターンの本をもう一度開いてみるべきかもしれませんが、適切なクラスをポリモーフィックにインスタンス化する方法があると本当に思いました。
列挙型?
私は抽象クラスの Type プロパティと列挙型を参照していました。
それでは反省です!他の人が検討する時間を与えるために、約 30 分以内に回答を承認済みとしてマークします。楽しい話題ですね。
オープンにしておいていただきありがとうございます、文句は言いません。これは楽しいトピックです。ポリモーフィックにインスタンス化できるといいのですが。
Ruby (およびその優れたメタプログラミング) でも、これにはリフレクション メカニズムを使用する必要があります。
@デール
私は nInject を厳密に検査したことはありませんが、依存性注入に関する私の高度な理解から、より多くの難解な層 (つまり抽象化) を追加するだけで、ChanChas の提案と同じことを達成できると考えています。
ここで必要になるだけの 1 回限りの状況では、リンクする追加のライブラリを用意して 1 か所でのみ呼び出すよりも、手動で作成したリフレクション コードを使用する方が良いアプローチだと思います。
しかし、おそらく私は、nInject がここで私にもたらす利点を理解していません。
一部のフレームワークは、必要に応じてリフレクションに依存する場合がありますが、ほとんどの場合、オブジェクトのインスタンスが必要になったときに何を行うかをセットアップするために、必要に応じてブートストラップを使用します。これは通常、汎用辞書に保存されます。Ninject を使い始めた最近まで、私は自分のものを使用していました。
Ninject で私が気に入った主な点は、リフレクションを使用する必要があるときに、リフレクションを使用しないことです。代わりに、.NET のコード生成機能を利用して、信じられないほど高速に実行します。使用しているコンテキストでリフレクションの方が速いと思われる場合は、そのように設定することもできます。
現時点で必要なものとしてはやりすぎかもしれないことは承知していますが、依存関係の注入について指摘し、将来に向けて考える材料を提供したいと思っています。訪問 道場 レッスンのために。