有静态抽象方法的替代方法是什么?

时间:2009-12-10 20:27:42

标签: c# interface static abstract-class

我在尝试找出如何在抽象类或接口中无法使用静态方法来解决问题时遇到了一些问题。请考虑以下代码。我有许多从AbsWizard继承的向导。每个向导都有一个方法GetMagic(字符串拼写),它只返回某些魔术字的魔法,但特定类型的向导的所有实例都会响应同一组魔术字。

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

我希望用户能够首先选择向导的类型,然后显示向导可以强制转换的法术列表。然后当他们选择一个咒语时,程序将找到所有类型的现有向导(如果有的话)并让他们施放所选的咒语。所有特定类型的向导都将具有相同的可用法术,我需要一种方法来确定特定类型的向导可以施放的法术,而实际上可以访问所选类型的向导的实例。

此外,我不想依赖于可能的向导类型或法术的单独列表。相反,我宁愿通过GetAvalibleSpells()和反射来推断所有内容。例如,我计划按如下方式施放魔法:

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

7 个答案:

答案 0 :(得分:2)

我认为这是非常糟糕的风格。你编写代码,所以你应该知道你在那里有什么向导类。通过反射运行所有类型并且检查它们是否来自AbsWizard是非常糟糕的样式(并且很慢!)。

答案 1 :(得分:1)

添加另一级别的间接。 GetAvailableSpells方法实际上不是实例方法,因为它对所有实例都是相同的。正如您所指出的那样,您不能拥有抽象的静态方法,因此将类型特定的东西移动到基于实例的类工厂中。在下面的示例中,AvailableSpellsMagicSchool抽象类的方法,它具有具体的子类BlackMagicWhiteMagic等。Wizard也有子类-types,但每个Wizard都可以返回它所属的MagicSchool,为您提供一种类型安全的,与类型无关的方法,以找出任何给定Wizard对象的法术是什么没有单独的表或代码重复。

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

答案 2 :(得分:1)

为此构建了Managed Extensibility Framework(通过codeplex for .NET-4.0或System.ComponentModel.Composition命名空间内置的.NET 4.0)。假设您有服务,可以要求用户选择向导然后创建它。它使用向导提供程序来创建向导,并且需要知道提供程序创建的向导的名称和可用法术(元数据)。您可以使用以下接口:

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

向导创建服务导入可用的向导提供程序,通过某种机制选择一种(在您的情况下为用户反馈),并使用提供程序创建向导。

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

然后,您可以使用法术创建并导出任意数量的向导提供者,创建服务将能够找到它们:

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

当然,你也需要实现向导。

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

为了保持清洁,此代码使用自定义NameAttributeSpellsAttribute作为导出元数据的方式比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; }
    }
}

答案 3 :(得分:0)

首先,你应该考虑是否不能弯曲不使用巫师实例发现其可用法术的规则。我发现prototype pattern对于这类事情实际上非常有用。

但是,如果你真的不能这样做,你可以使用嵌套类和反射来发现特定具体的AbsWizard衍生物可以施放的可用法术。这是一个例子:

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

有很多方法可以改进上述代码。

首先,您可以定义自己的自定义属性,您可以在每个向导的嵌套类上进行标记,以识别拼写词典。

其次,使用字符串来定义可用的法术可能最终会有点限制。您可能会发现更容易定义所有可用法术的全局静态列表(作为某种类,我们称之为Spell)。然后,您可以根据此列表定义向导的可用法术,而不是字符串。

第三,考虑为这个东西而不是嵌入式嵌套类创建外部配置。它更灵活,可能更容易维护。但是,编写如下代码可能会很好:

WhiteWizard.Spells.Abracadabra.Cast();

最后,考虑为管理可用法术列表的每个Wizard-derivative创建一个静态字典,这样就可以避免多次执行反射(价格昂贵)。

答案 4 :(得分:0)

由于法术与向导的类型相关联,我将通过属性执行此操作:

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

然后你声明一个这样的向导类型:

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

然后从程序集中加载向导类型的类可以检查每个向导类是否具有此属性,如果是,则使类型可用(或抛出异常)。

答案 5 :(得分:0)

这可以满足您的需求吗?根据需要将每种类型的向导添加到工厂。奇才永远不会在你的图书馆外实例化,只在它里面。对于你图书馆以外的人来获取巫师,他们会打电话给工厂以获得那些支持给定法术的巫师。工厂自行设定。只需在工厂注册每个新向导。

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

答案 6 :(得分:0)

使用工厂类来实例化向导。工厂有一个

public static string[] GetSpellsForWizardType(Type wizardType)

允许您确定向导可以施放哪些法术的方法。工厂也调用同样的方法来构造一个新的向导实例并设置它的法术集。