2009-12-10 14 views
5

Ich habe einige Probleme, herauszufinden, wie man ein Problem löst, ohne statische Methode in einer abstrakten Klasse oder Schnittstelle zu haben. Betrachten Sie den folgenden Code. Ich habe viele Wizards, die von AbsWizard erben. Jeder Zauberer hat eine Methode GetMagic (string spell), die nur Magie für bestimmte magische Wörter zurückgibt, aber alle Instanzen eines bestimmten Zauberertyps reagieren auf denselben Satz magischer Wörter.Was ist eine Alternative zu statischen abstrakten Methoden?

public abstract class AbsWizard 
{ 
    public abstract Magic GetMagic(String magicword); 
    public abstract string[] GetAvalibleSpells(); 
} 

public class WhiteWizard : AbsWizard 
{ 
    public override Magic GetMagic(string magicword) 
    { 
     //returns some magic based on the magic word 
    } 

    public override string[] GetAvalibleSpells() 
    { 
     string[] spells = {"booblah","zoombar"}; 
     return spells; 
    } 
} 

public class BlackWizard : AbsWizard 
{ 
    public override Magic GetMagic(string magicword) 
    { 
     //returns some magic based on the magic word 
    } 

    public override string[] GetAvalibleSpells() 
    { 
     string[] spells = { "zoogle", "xclondon" }; 
     return spells; 
    } 
} 

Ich möchte, dass die Benutzer in der Lage sein, zunächst die Art des Assistenten zu wählen und dann mit einer Liste der Zauber präsentiert diese Art von Assistenten werfen können. Wenn sie dann einen Zauberspruch auswählen, findet das Programm alle vorhandenen Zauberer des ausgewählten Typs und lässt sie den ausgewählten Zauber wirken. Alle Wizards eines bestimmten Typs haben immer die gleichen verfügbaren Spells, und ich brauche eine Möglichkeit, um die Spells zu bestimmen, die ein bestimmter Wizard-Typ ohne Zugriff auf eine Instanz des ausgewählten Wizard-Typs ausgeben kann.

Zusätzlich möchte ich nicht auf eine separate Liste von möglichen Assistentenarten oder Sprüchen angewiesen sein. Stattdessen würde ich lieber alles durch GetAvalibleSpells() und Reflektion ableiten. Zum Beispiel plane ich, Magie wie folgt zu werfen:

public static void CastMagic() 
    { 
     Type[] types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes(); 
     List<Type> wizardTypes = new List<Type>(); 
     List<string> avalibleSpells = new List<string>(); 

     Type selectedWizardType; 
     string selectedSpell; 

     foreach (Type t in types) 
     { 
      if (typeof(AbsWizard).IsAssignableFrom(t)) 
      { 
       wizardTypes.Add(t); 
      } 
     } 

     //Allow user to pick a wizard type (assign a value to selectedWizardType) 

     //find the spells the selected type of wizard can cast (populate availibleSpells) 

     //Alow user to pick the spell (assign a value to selectedSpell) 

     //Find all instances, if any exsist, of wizards of type selectedWizardType and call GetMagic(selectedSpell); 
    } 
+0

seine 'Available', nicht' Avalible': P –

+1

@ Andreas Grech - Warum nicht reparieren? –

+4

@Andreas es ist "es ist", nicht "es": P –

Antwort

1

Die Managed Extensibility Framework (verfügbar über Codeplex für Pre-.NET-4.0 oder integrierte .NET 4.0 im Namespace System.ComponentModel.Composition) wurde dafür erstellt. Angenommen, Sie haben einen Dienst, der einen Benutzer auffordern kann, einen Assistenten auszuwählen und ihn dann zu erstellen. Es verwendet einen Assistenten Provider, um die Assistenten zu erstellen, und muss den Namen und die verfügbaren Spells (Metadaten) für die Assistenten, die ein Anbieter erstellt, kennen. Sie könnten Schnittstellen wie diese verwenden:

namespace Wizardry 
{ 
    using System.Collections.Generic; 

    public interface IWizardProvider 
    { 
     IWizard CreateWizard(); 
    } 

    public interface IWizard 
    { 
     IMagic GetMagic(string magicWord); 
    } 

    public interface IWizardProviderMetadata 
    { 
     string Name { get; } 

     IEnumerable<string> Spells { get; } 
    } 
} 

Der Assistent Erstellung Service Importe die verfügbaren Assistenten Anbieter wählt man durch einen Mechanismus (Nutzer-Feedback in Ihrem Fall), und verwendet die Anbieter den Assistenten zu erstellen.

namespace Wizardry 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.ComponentModel.Composition; 
    using System.Linq; 

    public class UserWizardCreationService 
    { 
     [Import] 
     private IEnumerable<Lazy<IWizardProvider, IWizardProviderMetadata>> WizardProviders { get; set; } 

     public IWizard CreateWizard() 
     { 
      IWizard wizard = null; 
      Lazy<IWizardProvider, IWizardProviderMetadata> lazyWizardProvider = null; 
      IWizardProvider wizardProvider = null; 

      // example 1: get a provider that can create a "White Wizard" 
      lazyWizardProvider = WizardProviders.FirstOrDefault(provider => provider.Metadata.Name == "White Wizard"); 
      if (lazyWizardProvider != null) 
       wizardProvider = lazyWizardProvider.Value; 

      // example 2: get a provider that can create a wizard that can cast the "booblah" spell 
      lazyWizardProvider = WizardProviders.FirstOrDefault(provider => provider.Metadata.Spells.Contains("booblah")); 
      if (lazyWizardProvider != null) 
       wizardProvider = lazyWizardProvider.Value; 

      // finally, for whatever wizard provider we have, use it to create a wizard 
      if (wizardProvider != null) 
       wizard = wizardProvider.CreateWizard(); 

      return wizard; 
     } 
    } 
} 

Sie dann und eine beliebige Anzahl von Assistenten Anbieter Export mit Zaubern schaffen, und die Schaffung Dienst wird in der Lage sein, sie zu finden:

namespace Wizardry 
{ 
    using System.ComponentModel.Composition; 

    [Export(typeof(IWizardProvider))] 
    [Name("White Wizard")] 
    [Spells("booblah", "zoombar")] 
    public class WhiteWizardProvider : IWizardProvider 
    { 
     public IWizard CreateWizard() 
     { 
      return new WhiteWizard(); 
     } 
    } 

    [Export(typeof(IWizardProvider))] 
    [Name("White Wizard")] 
    [Spells("zoogle", "xclondon")] 
    public class BlackWizardProvider : IWizardProvider 
    { 
     public IWizard CreateWizard() 
     { 
      return new BlackWizard(); 
     } 
    } 
} 

Natürlich müssen Sie implementieren die Zauberer auch.

namespace Wizardry 
{ 
    using System; 

    public class WhiteWizard : IWizard 
    { 
     public IMagic GetMagic(string magicWord) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    public class BlackWizard : IWizard 
    { 
     public IMagic GetMagic(string magicWord) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
} 

Um die Dinge sauber zu halten, wird in diesem Code eine benutzerdefinierte NameAttribute und SpellsAttribute als viel sauberer Form Exportieren von Metadaten als ExportMetadataAttribute:

namespace Wizardry 
{ 
    using System; 

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] 
    public abstract class MultipleBaseMetadataAttribute : Attribute 
    { 
    } 

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] 
    public abstract class SingletonBaseMetadataAttribute : Attribute 
    { 
    } 

    public sealed class NameAttribute : SingletonBaseMetadataAttribute 
    { 
     public NameAttribute(string value) { this.Name = value; } 
     public string Name { get; private set; } 
    } 

    public sealed class SpellsAttribute : MultipleBaseMetadataAttribute 
    { 
     public SpellsAttribute(params string[] value) { this.Spells = value; } 
     public string[] Spells { get; private set; } 
    } 
} 
2

Ich denke, das ist sehr schlecht Stil. Du schreibst den Code, also solltest du wissen, welche Wizard-Klassen du dort hast. Es ist sehr schlecht Stil (und langsam!) Durch alle Arten durch Reflexion zu durchlaufen und zu überprüfen, ob sie von AbsWizard abstammen.

+0

Ich weiß welche Wizard-Klassen verfügbar sind, aber wenn ich eine neue Wizard-Klasse hinzufüge, versuche ich zu vermeiden, dass ich daran denke, einige externe Listen von Wizard-Typen und verfügbaren Spells zu aktualisieren, auf denen die UI basiert. –

+0

Auch würde ich es wahrscheinlich nicht so machen. Ich würde alle Typen mit Reflektion durchlaufen, wenn das Programm startet und dann die Liste der Verwendung später speichern. –

0

Zuerst sollten Sie wirklich überlegen, ob Sie nicht die Regeln verbieten können, Instanzen von Wizards nicht zu verwenden, um ihre verfügbaren Zauber zu entdecken. Ich finde, dass die prototype pattern tatsächlich sehr nützlich für diese Art von Sache sein kann.

Wenn Sie das wirklich nicht können, können Sie verschachtelte Klassen und Reflektionen verwenden, um die verfügbaren Spells zu ermitteln, die ein bestimmtes konkretes AbsWizard-Derivat liefern kann. Hier ein Beispiel:

public abstract class AbsWizard 
{ 
    public abstract Magic GetMagic(String magicword); 
    public abstract string[] GetAvalibleSpells(); 
} 

public class WhiteWizard : AbsWizard 
{ 
    // organizes all the spells available to the wizard... 
    public sealed class Spells 
    { 
     // NOTE: Spells may be better off as a specific class, rather than as strings. 
     // Then you could decorate them with a lot of other information (cost, category, etc). 
     public const string Abracadabra = "Abracadabra"; 
     public const string AlaPeanutButterSandwiches = "APBS"; 
    } 
} 

public static void CastMagic() 
{ 
    Type[] types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes(); 
    List<Type> wizardTypes = new List<string>(); 
    List<string> avalibleSpells = new List<string>(); 

    Type selectedWizardType; 
    string selectedSpell; 

    foreach (Type t in types) 
    { 
     if (typeof(AbsWizard).IsAssignableFrom(t)) 
     { 
      // find a nested class named Spells and search it for public spell definitions 
      // better yet, use an attribute to decorate which class is the spell lexicon 
      var spellLexicon = Type.FromName(t.FullName + "+" + "Spells"); 
      foreach(var spellField in spellLexicon.GetFields()) 
       // whatever you do with the spells... 
     } 
    } 
} 

Es gibt viele Möglichkeiten, den obigen Code zu verbessern.

Zunächst können Sie ein benutzerdefiniertes Attribut definieren, das Sie in den verschachtelten Klassen jedes Assistenten markieren können, um das Buchstablexikon zu identifizieren.

Zweitens kann die Verwendung von Strings zur Definition der verfügbaren Spells am Ende ein wenig einschränkend sein. Es ist vielleicht einfacher, eine globale statische Liste aller verfügbaren Sprüche zu definieren (als eine Art Klasse, nennen wir es Spell). Sie könnten dann die verfügbaren Spells des Assistenten basierend auf dieser Liste statt Strings definieren.

Drittens, erstellen Sie eine externe Konfiguration für diese Sache lieber als eingebettete, verschachtelte Klassen. Es ist flexibler und möglicherweise einfacher zu warten. Allerdings kann es schön sein, Code zu schreiben wie:

WhiteWizard.Spells.Abracadabra.Cast(); 

Schließlich betrachtet ein statisches Wörterbuch für jeden Wizard-Derivat zu schaffen, die die Liste der verfügbaren Zauber verwaltet, so dass Sie Reflexion Durchführung vermeiden können (was teuer ist) mehr als einmal.

+0

Sie haben vergessen, das Beispiel einzuschließen. –

+0

Ich kann einen "Wizard" nicht instanziieren. Ich habe nicht wirklich Zauberer, ich habe nur versucht, ein einfacheres Beispiel für den Zweck der Frage zu machen. Das ganze Programm ist eigentlich ein Plug-in für ein anderes Programm, bei dem meine Klasse eine Klasse aus der API des Gesamtprogramms umschließt. –

1

Fügen Sie eine weitere Indirektionsstufe hinzu. Die GetAvailableSpells-Methode ist nicht wirklich eine Instanzmethode, da sie für alle Instanzen gleich ist. Wie Sie auf Sie hingewiesen haben, können Sie keine abstrakte statische Methode verwenden, sondern verschieben Sie stattdessen die typspezifischen Daten in eine auf Instanzen basierende Klassenfactory.Im Beispiel unten ist AvailableSpells ein Verfahren der MagicSchool abstrakte Klasse, die BlackMagic konkrete Subklassen hat, WhiteMagic usw. Die Wizard auch Untertypen hat, aber jeder Wizard können die MagicSchool zurückkehren, dass es gehört, Sie eine Typ- geben sichere, typunabhängige Art und Weise herauszufinden, welche Spells für ein gegebenes Wizard Objekt ohne separate Tabellen oder Code-Duplikation sind.

public abstract class MagicSchool 
{ 
    public abstract string[] AvailableSpells { get; } 
    public abstract Wizard CreateWizard(); 
} 

public abstract class Wizard 
{ 
    protected Wizard(MagicSchool school) 
    { 
     School = school; 
    } 

    public abstract Cast(string spell); 

    MagicSchool School 
    { 
     public get; 
     protected set; 
    } 
} 

public class BlackMagic : MagicSchool 
{ 
    public override AvailableSpells 
    { 
     get 
     { 
      return new string[] { "zoogle", "xclondon" }; 
     } 
    } 

    public override Wizard CreateWizard() 
    { 
     return new BlackWizard(this); 
    } 
} 

public class BlackWizard : Wizard 
{ 
    public BlackWizard(BlackMagic school) 
     : base(school) 
    { 
     // etc 
    } 

    public override Cast(string spell) 
    { 
     // etc. 
    } 
} 

// continue for other wizard types 
+0

Ich sehe, was Sie sagen, aber ich kann nicht einfach einen Assistenten beliebig erstellen. In Wirklichkeit ist meine Klasse Teil eines Plug-ins für ein anderes Programm und umschließt eine Klasse von der API dieses Programms. Daher hängt der Konstruktor für meine AbsWizard-Klasse davon ab, eine Instanz einer Klasse aus der API zu haben. –

+0

Also machen Sie diesen Teil des Konstruktors für 'MagicSchool' oder das Äquivalent davon ... Ich sehe nicht, wie das ein großes Problem ist. –

0

Da Zauber auf die Art des Assistenten gebunden sind, würde ich dies durch Attribute tun:

[Spells("booblah","zoombar")] 
public class WhiteWizard : AbsWizard 
{ 
    public override Magic GetMagic(string magicWord) { ... } 
} 

Dann:

[AttributeUsage(AttributeTargets.Class)] 
public class SpellsAttribute : Attribute 
{ 
    private string[] spells; 
    public WizardAttribute(params string[] spells) 
    { 
     this.spells = spells; 
    } 

    public IEnumerable<string> Spells 
    { 
     get { return this.spells ?? Enumerable.Empty<string>(); } 
    } 
} 

Dann Sie einen Assistenten Typ wie folgt erklären Die Klasse, die die Wizard-Typen aus der Assembly lädt, kann prüfen, ob jede Wizard-Klasse dieses Attribut besitzt. Wenn dies der Fall ist, wird der Typ verfügbar (oder löst eine Ausnahme aus).

0

Macht das, was Sie brauchen? Fügen Sie nach Bedarf jeden Wizard-Typ zur Factory hinzu. Assistenten werden niemals außerhalb Ihrer Bibliothek, sondern nur innerhalb davon instanziiert. Wenn jemand außerhalb deiner Bibliothek einen Zauberer erhält, ruft er die Fabrik an, um die Zauberer zu erhalten, die einen bestimmten Zauberspruch unterstützen. Die Fabrik stellt sich auf. Registrieren Sie einfach jeden neuen Assistenten im Werk.

public class Magic 
{ 
} 

public abstract class AbsWizard 
{ 
    public abstract Magic GetMagic(String magicword); 
    public abstract string[] GetAvalibleSpells(); 

    internal AbsWizard() 
    { 
    } 
} 

public class WhiteWizard : AbsWizard 
{ 
    public override Magic GetMagic(string magicword) 
    { 
     return new Magic(); 
    } 

    public override string[] GetAvalibleSpells() 
    { 
     string[] spells = { "booblah", "zoombar" }; 
     return spells; 
    } 
} 


public static class WizardFactory 
{ 
    private static Dictionary<string, List<AbsWizard>> _spellsList = new Dictionary<string, List<AbsWizard>>(); 

    /// <summary> 
    /// Take the wizard and add his spells to the global spell pool. Then register him with that spell. 
    /// </summary> 
    /// <param name="wizard"></param> 
    private static void RegisterWizard(AbsWizard wizard) 
    { 
     foreach (string s in wizard.GetAvalibleSpells()) 
     { 
      List<AbsWizard> lst = null; 
      if (!_spellsList.TryGetValue(s, out lst)) 
      { 
       _spellsList.Add(s, lst = new List<AbsWizard>()); 
      } 
      lst.Add(wizard); 
     } 
    } 

    public string[] GetGlobalSpellList() 
    { 
     List<string> retval = new List<string>(); 
     foreach (string s in _spellsList.Keys) 
     { 
      retval.Add(s); 
     } 
     return retval.ToArray<string>(); 
    } 

    public List<AbsWizard> GetWizardsWithSpell(string spell) 
    { 
     List<AbsWizard> retval = null; 
     _spellsList.TryGetValue(spell, out retval); 
     return retval; 
    } 

    static WizardFactory() 
    { 
     RegisterWizard(new WhiteWizard()); 
    } 
} 
0

Verwenden Sie eine Factory-Klasse, um Ihre Assistenten zu instanziieren. Die Factory verfügt über eine Methode, mit der Sie bestimmen können, welche Zauber ein Zauberer wirken kann. Die Factory ruft diese Methode auch auf, um eine neue Zaubererinstanz zu erstellen und ihren Zauberspruch festzulegen.

Verwandte Themen