English 中文(简体)
What is an alternative to having static abstract methods?
原标题:

I m having some problems trying to figure out how to solve a problem without being able to have static method in an abstract class or interface. Consider the following code. I have many Wizards that inherit from AbsWizard. Each wizard has a method GetMagic(string spell) that only returns magic for certain magic words, yet all instances of a specific type of wizard respond to the same set of magic words.

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;
    }
}

I want the user to be able to first choose the type of wizard, and then be presented with a list of the spells that type of wizard can cast. Then when they choose a spell the program will find all, if any, existing wizards of the selected type and have them cast the selected spell. All wizards of a specific type will always have the same available spells, and I need a way to determine the spells a specific type of wizard can cast with out actually having access to an instance of the selected type of wizard.

In addition I don t want to have to depend on a separate list of possible wizard types or spells. Instead I would rather just infer everything through GetAvalibleSpells() and reflection. For example I plan to cast magic as follows:

    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);
    }
最佳回答

The Managed Extensibility Framework (available through codeplex for pre-.NET-4.0, or built-in .NET 4.0 in the System.ComponentModel.Composition namespace) was built for this. Say you have a service that can ask a user to select a wizard and then create it. It uses a wizard provider to create the wizards, and needs to know the name and available spells (metadata) for the wizards that a provider creates. You might use interfaces like these:

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; }
    }
}

The wizard creation service imports the available wizard providers, selects one through some mechanism (user feedback in your case), and uses the provider to create the wizard.

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;
        }
    }
}

You can then create and export an arbitrary number of wizard providers with spells, and the creation service will be able to find them:

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();
        }
    }
}

Of course you ll need to implement the wizards as well.

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();
        }
    }
}

To keep things clean, this code uses a custom NameAttribute and SpellsAttribute as a much cleaner form of exporting metadata than 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; }
    }
}
问题回答

I think this is very bad style. You write the code, so you should know what wizard-classes you have in there. It s very bad style (and slow!) to run through all types via reflection and check if they derive from AbsWizard.

Add another level of indirection. The GetAvailableSpells method isn t really an instance method, since it s the same for all instances. As you pointed you, you can t have an abstract static method, so instead move the type-specific stuff into an instance-based class factory. In the example below, AvailableSpells is a method of the MagicSchool abstract class, which has concrete subclasses BlackMagic, WhiteMagic, etc. The Wizard also has sub-types, but every Wizard can return the MagicSchool that it belongs to, giving you a type-safe, type-independent way to find out what the spells for any given Wizard object are without separate tables or code duplication.

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

First, you should really consider whether you can t bend the rules of not using instances of Wizards to discover their available spells. I find that the prototype pattern can actually be quite useful for this sort of thing.

However, if you really can t do that, you can use nested classes and reflection to discover the available spells that a particular concrete AbsWizard-derivative can cast. Here s an example:

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...
        }
    }
}

There are many ways to improve the above code.

First, you can define your own custom attribute that you can tag on the nested classes of each wizard to identify the spell lexicon.

Second, using strings to define the available spells may end up being a bit limiting. You may find it easier to define a global static list of all available spells (as some kind of class, let s call it Spell). You could then define the available spells of the wizard based off this list, rather than strings.

Third, consider creating an external configuration for this thing rather than embedded, nested classes. It s more flexible and possibly easier to maintain. However, it can be nice to write code like:

WhiteWizard.Spells.Abracadabra.Cast();

Finally, consider creating a static dictionary for each Wizard-derivative that manages the list of available spells so that you can avoid performing reflection (which is expensive) more than once.

Since spells are tied to the type of the wizard, I d do this through attributes:

[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>(); }
    }
}

Then you declare a wizard type like this:

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

Then the class that loads wizard types from the assembly can check each wizard class has this attribute and if so makes the type available (or throws an exception).

Does this do what you need? Add each type of wizard to the factory as needed. Wizards will never be instantiated outside of your library, only inside it. For someone outside your library to get a wizard, they make a call to the factory to get those wizards that support a given spell. The factory sets itself up. Just register each new wizard with the factory.

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());
    }
}

Use a factory class to instantiate your wizards. The factory has a

public static string[] GetSpellsForWizardType(Type wizardType)

method that allows you to determine which spells a wizard can cast. The factory also calls this same method to construct a new wizard instance and set its spell set.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签