C # — Как обозначить перечисление как должно быть реализовано в абстрактном классе?

#c# #enums #factory

#c# #перечисления #фабрика

Вопрос:

Например, у меня есть фабричный класс:

 abstract class UnitFactory<T> where T:Unit
{
   abstract enum UnitType;

   public abstract T GetUnit(UnitType type);
}
  

Здесь производный класс должен иметь перечисление (конечно abstract enum , не работает), чтобы указать, какую единицу он может создать, вместо набора строк, которые, я думаю, трудно применять / управлять.

Итак, мой вопрос в том, как мне создать «абстрактное» перечисление, подобное этому? Или, если это невозможно, как лучше всего сделать что-то подобное?

Извините за мой плохой английский и мой, казалось бы, глупый вопрос.

Редактировать: пример дочернего класса:

 class ArcherFactory : UnitFactory<Archer>
{
   private static Archer _baseLongbowman = ....;
   private static Archer _baseCrossbowman = ....;

   // Child class must have a implementation of UnitType enum to
   // tell the user that it can only make those kind of units.
   public enum UnitType{ Longbowman, Crossbowman }

   public override Archer getUnit(UnitType type)
   {
      if (type == UnitType.Longbowman) return _baseLongbowman.Clone(...);
      return _baseCrossbowman.Clone(...);
   }
}
  

Комментарии:

1. Что это abstract enum значит? Перечисления не поддерживают наследование. Это просто значения

2. перечисление — это тип значения, к сожалению, поэтому вы ничего не можете извлечь из него

3. Это похоже на случай проблемы XY . Возникает проблема с X, предполагая, что Y является решением (абстрактные перечисления), и когда это не работает, запрашивается Y, а не фактическая проблема X. В чем фактическая проблема?

4. Я понимаю, я просто хочу иметь что-то вроде «абстрактного перечисления», должно быть реализовано перечисление.

5. @Tr1et: что такое «реализованное перечисление»? Приведите пример того, как вы могли бы 1) создать несколько конкретных фабрик и 2) вызвать GetUnit несколько комбинаций фабрик / типов единиц.

Ответ №1:

Вы должны определить два общих типа для вашей абстрактной фабрики

 public abstract class UnitFactory<TType, TUnit> where TUnit:Unit
{
    public abstract TUnit GetUnit(TType type);
}
  

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

 public enum ArcherType { Longbowman, Crossbowman }
  

И в конечном итоге создайте фабрику archer.

 public class ArcherFactory : UnitFactory<ArcherType, Archer>
{
    private static Archer _baseLongbowman = ....;
    private static Archer _baseCrossbowman = ....;

    public override Archer GetUnit(ArcherType type)
    {
        switch (type)
        {
            case ArcherType.Crossbowman:
                return  _baseCrossbowman.Clone(...);

            default:
                return  _baseLongbowman.Clone(...);

        }
    }
}
  

Редактировать:

Вместо использования ваших статических экземпляров и клонирования вы можете использовать Func для создания отдельных экземпляров каждого модуля.

 public class ArcherFactory : UnitFactory<ArcherType, Archer>
{
    public ArcherFactory()
    {
        this.Register(ArcherType.Longbowman, () => new Archer(...));
        this.Register(ArcherType.Crossbowman, () => new Archer(...));
    }
}

public abstract class UnitFactory<TType, TUnit>
{
    private readonly Dictionary<TType, Func<TUnit>> factoryMethods = new Dictionary<TType, Func<TUnit>>();

    protected void Register(TType type, Func<TUnit> constructorFuc)
    {
        // perform some sanity checks
        this.factoryMethods.Add(type, constructorFuc);
    }

    public TUnit GetUnit(TType type)
    {
        // perform some sanity checks
        return this.factoryMethods[type]();
    }
}
  

И используйте это

 var archerFactory = new ArcherFactory();
var crossbowMan = archerFactory.GetUnit(ArcherType.Crossbowman); 
  

Комментарии:

1. Блестяще!!!! Я действительно не думал о передаче перечисления в качестве параметра типа. Но могу ли я ограничить параметр первого типа перечислением?

2. Хотя вы должны быть в порядке с одним перечислением UnitType и регистрацией разных делегатов конструктора только для 1 фабрики с разными значениями enum… так что можно просто использовать UnitFactory. Создать(UnitType. LongBowman). Также нет необходимости в клонировании и т. Д.

3. Добавлен пример регистрации функций на фабрике, что облегчит вашу жизнь, не беспокоясь о клонировании.

Ответ №2:

Требование, чтобы каждая конкретная фабрика предоставляла свой собственный список типов, означает, что все конкретные реализации будут привязаны только к этой фабрике.

Другими словами, нет абсолютно никакой пользы в выполнении:

 var longbowman = archerFactory.GetUnit(ArcherFactory.UnitType.Longbowman);
var crossbowman = archerFactory.GetUnit(ArcherFactory.UnitType.Crossbowman);
  

по сравнению с просто:

 // this is better than a huge switch/case method
var longbowman = archerFactory.GetLongbowman()
var crossbowman  = archerFactory.GetCrossbowman();
  

Чтобы шаблон абстрактной фабрики имел смысл, вам необходимо определить тип отдельно от фабрики, т.е.

 enum ArcherType { Longbowman, Crossbowman }

interface IFactory<Ttype, Tvalue>
{
    Tvalue GetUnit(Ttype);
}

interface IArcherFactory : IFactory<ArcherType, Archer>
{
    ...
}