Что альтернатива к наличию статических абстрактных методов?

Я не думаю, что там существует текущий, но существует [едва зарегистрировано] проект на Google Code с более старой версией...

5
задан Calvin Fisher 17 January 2012 в 18:33
поделиться

7 ответов

Managed Extensibility Framework (доступен через codeplex для pre-.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();
        }
    }
}

Чтобы все было в порядке, в этом коде используются пользовательские NameAttribute и SpellsAttribute как более чистая форма экспорта метаданных, чем 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; }
    }
}
1
ответ дан 15 December 2019 в 01:03
поделиться

Я считаю, что это очень плохой стиль. Вы пишете код, поэтому вы должны знать, какие классы-мастера у вас там есть. Очень плохой стиль (и медленный!) - обрабатывать все типы через отражение и проверять, происходят ли они от AbsWizard.

2
ответ дан 15 December 2019 в 01:03
поделиться

Добавьте еще один уровень косвенного обращения. Метод GetAvailableSpells на самом деле не является методом экземпляра, поскольку он одинаков для всех экземпляров. Как вы указали, у вас не может быть абстрактного статического метода, поэтому вместо этого переместите материал, зависящий от типа, в фабрику классов на основе экземпляров. В приведенном ниже примере AvailableSpells - это метод абстрактного класса MagicSchool , который имеет конкретные подклассы BlackMagic , WhiteMagic и т. Д. Мастер также имеет подтипы, но каждый Мастер может возвращать 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
1
ответ дан 15 December 2019 в 01:03
поделиться

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.

0
ответ дан 15 December 2019 в 01:03
поделиться

Поскольку заклинания привязаны к типу волшебника, я бы сделал это с помощью атрибутов:

[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
ответ дан 15 December 2019 в 01:03
поделиться

Это то, что вам нужно? При необходимости добавьте в фабрику мастера каждого типа. Мастера никогда не будут созданы вне вашей библиотеки, только внутри нее. Чтобы получить волшебника за пределами вашей библиотеки, он звонит на фабрику, чтобы получить тех волшебников, которые поддерживают данное заклинание. Завод налаживается. Просто зарегистрируйте каждого нового мастера на фабрике.

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
ответ дан 15 December 2019 в 01:03
поделиться

Используйте фабричный класс для создания экземпляров ваших мастеров. У фабрики есть метод

public static string[] GetSpellsForWizardType(Type wizardType)

, который позволяет вам определять, какие заклинания может использовать волшебник. Фабрика также вызывает этот же метод для создания нового экземпляра волшебника и установки его набора заклинаний.

0
ответ дан 15 December 2019 в 01:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: