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

5

我在尝试解决一个问题时遇到了困难:在抽象类或接口中不能使用静态方法。请看下面的代码。我有许多继承自AbsWizard的巫师,每个巫师都有一个GetMagic(string spell)方法,该方法仅返回某些魔法单词的魔法,但同一类型的所有巫师实例都响应相同的魔法单词。

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

它是“Available”,而不是“Avalible”:P - Andreas Grech
1
@Andreas Grech-为什么不修复它呢? - John MacIntyre
4
@Andreas 应该是 "it's",而不是 "its" :P - Sarah Vessels
2
我穿上了我的长袍和巫师帽。 - snicker
顺便提一下,整个类型搜索过程可以更加简洁地表达:System.Reflection.Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(AbsWizard).IsAssignableFrom(t)) - Calvin Fisher
7个回答

2

我认为这是非常糟糕的风格。你编写代码,所以你应该知道其中有哪些向导类。通过反射遍历所有类型并检查它们是否派生自AbsWizard是非常糟糕的风格(而且很慢!)。


我知道有哪些巫师类可用,但如果我添加一个新的巫师类,我想避免记住去更新一些外部巫师类型和可用法术列表,这些列表是基于UI的。 - Eric Anastas
我可能不会那样做。我会在程序启动时使用反射运行所有类型,然后保存使用列表以供以后使用。 - Eric Anastas

1

增加另一层间接性。由于GetAvailableSpells方法对于所有实例都是相同的,因此它并不是真正的实例方法。正如您所指出的,您无法拥有抽象静态方法,因此将类型特定的内容移动到基于实例的类工厂中。在下面的示例中,AvailableSpellsMagicSchool抽象类的一个方法,该类具有具体子类BlackMagicWhiteMagic等。Wizard也有子类型,但每个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

我明白你的意思,但我不能随意创建一个向导。实际上,我的类是另一个程序插件的一部分,并包装了该程序API中的一个类。因此,我的AbsWizard类的构造函数取决于具有API类实例。 - Eric Anastas
所以将其作为“MagicSchool”的构造函数的一部分或等效部分...我不认为这是一个大问题。 - JSBձոգչ

1

托管可扩展框架(通过codeplex提供给.NET 4.0之前的版本,或内置于.NET 4.0的System.ComponentModel.Composition命名空间中)就是为此而建立的。假设您有一个服务,可以要求用户选择向导,然后创建它。它使用向导提供程序来创建向导,并需要知道提供程序创建的向导的名称和可用咒语(元数据)。您可以使用以下接口:

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

0

首先,你应该认真考虑一下是否可以不使用巫师实例来发现它们可用的咒语规则。我发现原型模式实际上对这种事情非常有用。

然而,如果你真的无法这样做,你可以使用嵌套类和反射来发现特定具体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-衍生类创建一个静态字典,以管理可用咒语列表,这样您就可以避免执行反射(这是昂贵的)超过一次。

你忘记包含示例了。 - Maximilian Mayerl
我无法实例化“向导”。实际上,我并没有向导,我只是为了问题的简单化而尝试制作一个更简单的示例。整个程序实际上是另一个程序的插件,我的类包装了整个程序API中的一个类。 - Eric Anastas

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

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


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

0
使用工厂类来实例化您的巫师。该工厂具有一个
public static string[] GetSpellsForWizardType(Type wizardType)

这是一种允许您确定巫师可以施放哪些咒语的方法。工厂还会调用此方法来构建新的巫师实例并设置其咒语集。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接