4

私が直面している問題を説明するために、少し抽象的なドメインを作成しました。

プレイヤーが軍隊の将軍である中世のゲームがあり、戦闘全体は、戦闘が始まる前に、たとえば準備モードで作成された戦闘計画によってほとんど影響を受けます。

必要なものを実現するために、インターフェイスを作成し、IBattleUnit物事を非常にシンプルに保ちました。

public interface IBattleUnit
{
    void Move();
    void Attack();
    string Salute();
}

3 種類のユニットを用意することで、今のところ仕事ができるのでArcher.cs、ほとんど同じ方法でインターフェイスPikeman.csを実装します。Swordsman.cs

public class Swordsman : IBattleUnit
{
    private Swordsman() {}

    public void Move()
    {
        //swordsman moves
    }

    public void Attack()
    {
        //swordsman attacks
    }

    public string Salute()
    {
        return "Swordsman at your service, master.";
    }
}

プライベートコンストラクターに注意してください。これは、でのみ採用される戦闘ユニットを対象としていますBarracks。これは一般的なファクトリです。

public static class Barracks<T> where T : class, IBattleUnit
{
    private static readonly Func<T> UnitTemplate = Expression.Lambda<Func<T>>(
       Expression.New(typeof(T)), null).Compile();

    public static T Recruit()
    {
        return UnitTemplate();
    }
}

注: 空のコンストラクターのプリコンパイル済みラムダ式により、(私のマシン上で) ユニットの作成が高速化されますが、軍隊は非常に大きくなる可能性がありますが、高速なジェネリックの作成はまさに私が達成したいことです。

戦闘を開始するために必要なすべてをカバーしたため、BattlePlan説明が唯一の欠けている部分であるため、ここで説明します。

public static class BattlePlan
{
    private static List<Type> _battleUnitTypes;
    private static List<Type> _otherInterfaceImplementors;
    //...

    private static Dictionary<string, string> _battlePlanPreferences;

    private static Type _preferedBattleUnit;
    private static Type _preferedTransportationUnit;
    //...

    static BattlePlan()
    {
        //read the battle plan from file (or whereever the plan init data originate from)
        //explore assemblies for interface implementors of all kinds
        //and finally fill in all fields
        _preferedBattleUnit = typeof (Archer);
    }

    public static Type PreferedBattleUnit
    {
        get
        {
            return _preferedBattleUnit;
        }
    }

    //... and so on

}

これに到達した場合は、ドメイン全体を認識しています-コンパイルも行われ、すべてが明るく見えます...

今まで: 私はコンソール アプリケーションを作成し、上記への参照を追加し、ボンネットの下にあるものから利益を得ようとします。私の混乱を完全に説明するために、最初に何が機能しているかに注意してください。

  • 兵舎に特定の BattleUnit を与えてもらいたい場合は、それをインスタンス化して、戦わせ、移動させ、敬礼させることができます。インスタンス化が次のように行われる場合:
IBattleUnit unit = Barracks<Pikeman>.Recruit();
  • 戦闘計画に基づいて優先されるユニットが何であるかを知りたい場合は、それを取得できます。その を尋ねることができますAssemblyQualifiedName。タイプを取得します (実際にはArcherであり、 にとどまるのと同じですBattlePlan)。私が電話するとき、私は期待しています:
Type preferedType = BattlePlan.PreferedBattleUnit;

ここで、BattlePlan が Type を提供することを期待し、ある種の Unit をインスタンス化するために Type を Barracks に渡すだけで、VisualStudio2012 (現在のバージョンの resharper) が停止し、コードをコンパイルしませんが、コードは、エラーにつながるのは次のとおりです。

Type t = Type.GetType(BattlePlan.PreferedBattleUnit.AssemblyQualifiedName);
IBattleUnit u = Barracks<t>.Recruit();

私が何をしても、 を渡すか、tとして渡すかtypeof(t)、または に変換しようとしてIRepositoryも... エラーリストに (少なくとも) 2 つのエラーがあり、そのようなコードをコンパイルできません。

Error   1   Cannot implicitly convert type 't' to 'BattleUnits.cs.IBattleUnit' Program.cs
Error   2   The type or namespace name 't' could not be found (are you missing a using directive or an assembly reference?) Program.cs

実際の質問に:

  1. 基盤となるインフラストラクチャを変更せずに、タイプを Barracks に渡す方法はありますか?
  2. または、設計上間違っていることはありますか?

私は過去 2 日間、グーグルで検索を続けましたが、唯一明確な方法は兵舎を変更することでした。

編集番号 1 : コンセプトとすべてを再考するとき:IBattleUnitすべてのユニットが実行できるコア戦闘アクションのセットとして最初に説明されました (そして、私たちはそれをこのようにしたいと考えています)。GroundUnitBase私は基本クラスを導入したくありませんでした。理由は、FlyingUnitBase抽象クラスが存在する可能性があることを知っていたからです。明確で論理的な設計が必要です...しかし、 static は絶対に 1 つだけである必要がありますBarracks

BattleUnits についてはまだですが、1 つの基本クラスを目にすることで、実行可能なコードの状況が変わるUnitBase可能性があるように思えます。私はそれを試してみるつもりです ... 読んで、私が書いたことから、クラスについて考えるようになりました。設計ではなく、何らかの形でそのコンパイル可能性です。ですから、これは、書かれていることを再考した後、私の心に浮かんだ最初のアイデアです。

4

5 に答える 5

1

のインスタンスがある場合は、キーワードPreferedBattleUnitを使用するだけです。dynamicこの質問を見てください(John Skeetの回答):(編集:メソッドが一般的ではないため、これはあまり役に立たないかもしれません)

具体的なオブジェクト型をジェネリック メソッドのパラメーターとして渡す

オブジェクトのインスタンスがない場合は、次の質問を見てください (再び John Skeet の回答)。

変数の型をパラメーターとして使用する C# のジェネリック

于 2013-11-14T22:39:08.840 に答える
1
public static class Barracks
{
    public static IBattleUnit Recruit(Type preferredType)
    {
        return (IBattleUnit)typeof(Barracks<>).MakeGenericType(preferredType).GetMethod("Recruit", BindingFlags.Public|BindingFlags.Static).Invoke(null,null);
    }
}

それから電話する

Barracks.Recruit(BattlePlan.PreferredBattleUnit)
于 2013-11-14T22:25:29.687 に答える
1

次のようなリフレクションを使用してこれを行うことができます。

IBattleUnit unit = typeof(Barracks).GetMethod("Recruit").MakeGenericType(BattlePlan.PreferedBattleUnit).Invoke(null, null) as IBattleUnit;
于 2013-11-14T22:31:24.543 に答える
0

私の戦略はDictionary<Type, Barracks<IBattleUnit>>、すべての兵舎を定義してから取得しようとすることを前提として、 を作成することです。そうすれば、キーで一致させて安全にキャストできます。

これには、Barracks<> が静的クラスではないことが必要です。管理しているある種の外部リソースのような非常に具体的な理由がない限り (そしておそらくその場合でも)、おそらく静的クラスは必要ありません。

これらすべての静的を作成するとすべてが簡単になるように思えるかもしれませんが、最終的には、変更される可能性のあるリソースへの依存関係を作成します。別のユニット タイプを発明した場合は、それを兵舎に登録する必要があります。これは、基本クラスを作成したくない理由とまったく同じであり、忘れると例外がスローされ、さらに悪いことになります。 、それは最小の驚きの原則に違反しているためです。

于 2013-11-14T23:50:22.747 に答える