Шаблон проектирования Factory и реализация внедрения зависимостей

В настоящее время я пытаюсь реализовать некоторые проектные структуры, и фабрика кажется наиболее подходящей, и что касается внедрения зависимостей, я предпочитаю внедрение конструктора. Однако возникает проблема, что не все мои продукты требуют одинаковых зависимостей, что нарушает шаблон...

Мой абстрактный метод Factory .get() должен выглядеть так

abstract class AbstractSpellFactory
{
    public abstract WCSpell get(SpellSubType sSubType,SpellCard,int a,int b,int c,int d);
}

и для завершения вот класс заклинаний в контексте того, что я делаю/использую

public abstract class WCSpell
{
    public abstract void CastSpell();
}

и тогда я могу использовать его как

AbstractSpellFactory aSpellFactory = SpellFactory.createSpellFactory(SpellType.buff);
WCSpell spell = aSpellFactory.get(SpellSubType.Positive,sCard,1,2,3,4);//OK
spell.CastSpell();

aSpellFactory = SpellFactory.createSpellFactory(SpellType.rotate);
spell = aSpellFactory.get(SpellSubType.clockwise,sCard,0,0,0,0);//Non-used/Needed values...
spell.CastSpell();

Так что это работает, но в первую очередь тот факт, что заклинание поворота не нуждается в целых числах, немного неэлегантно, но самая большая проблема из всех заключается в том, что если я добавлю какие-либо новые заклинания, которые имеют разные зависимости, параметры аргумента метода AbstractSpellFactory.get(...) просто будут становится больше, и в зависимости от типа заклинания он может даже не нуждаться в передаваемых значениях.

Так что я немного застрял, у кого-нибудь есть предложения?

Псевдо-реализация кода выше

Класс Фабрики заклинаний

static class SpellFactory
{
    public static AbstractSpellFactory createSpellFactory( SpellType sType )
    {
        AbstractSpellFactory sFactory = null;

        switch(sType)
        {
            case SpellType.kBuff:
            {
                sFactory = new SpellBuffFactory();
            }
                break;

            case SpellType.kRotateClockWise:
            {
                sFactory = new SpellRotateFactory();
            }
                break;
        }

        return sFactory;
    }
}

Бафф Фабрика заклинаний

public class SpellBuffFactory : AbstractFactory
{
    public override Spell get( SpellSubType sSubType,SpellCard sCard,int a,int b,int c,int d)
    {
        Spell spell = null;

        switch(sSubType)
        {
            case Positive:
            {
                spell = new BuffSpell(a,b,c,d,sCard);
            }
                break;

            case Negative:
            {
                spell = new BuffSpell(-a,-b,-c,-d,sCard);//some check to make sure all values are negative
            }
        }

        return spell;
    }
}

Повернуть фабрику заклинаний

public class SpellRotateFactory : AbstractFactory
{
    public override Spell get( SpellSubType sSubType,SpellCard sCard,int a,int b,int c, int d)
    {
        Spell spell = null;

        switch(sSubType)
        {
            case Clockwise:
            {
                spell = new WCRotateSpell(WCRotateSpell.RotationDirection.Clockwise,sCard);
            }
                break;

            case CounterClockwise:
            {
                spell = new WCRotateSpell(WCRotateSpell.RotationDirection.CounterClockwise,sCard);
            }
        }

        return spell;
    }
}

person Genhain    schedule 19.05.2014    source источник


Ответы (1)


Всякий раз, когда я вижу такое количество параметров, я думаю, что что-то можно улучшить. Добавление вашей действительной заботы о внедрении зависимостей и новых функциях только делает более важным рассмотрение ваших вариантов, которые, как я вижу, заключаются в следующем:

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

    public interface ICastable()
    {
        bool ValidateTarget();
    
        // ICastable will require implementors to define Cast;
        // forcing a descendant (or the ancestor, I suppose) to
        // provide the details of how to cast that spell.  The parameter
        // is also a type that you control for spell-casting information
        void Cast(InvocationInfo info);
    }
    
    // really common info needed to cast a spell
    public class InvocationInfo
    {
        TargetableEntity Target;
        ControllableEntity SpellCaster;
        List<SpellRegents> ReagentsChosen;
        MoonPhases MoonPhase;
        bool IsMercuryInRetrograde;
    }
    
    // base spell class
    public class Spell
    {
        public string Name { get; set; }
        public int EnergyCost { get; set; }
    }
    
    // actual castable spell
    public class MagicMissile : Spell, ICastable
    {
        public void Cast(InvocationInfo details)
        {
            details.SpellCaster.SpendMana(this.EnergyCost);
    
            if (details.Target.Location.DistanceFrom(details.SpellCaster) > this.Range)
            {
                details.SpellCaster.SendMessage(Messages.OutOfRange);
                return;
            }
            // ...
        }
    }
    
  2. Не забывайте, что вы можете использовать общий тип:

    public Spell Hang<T>(InvocationInfo details) where T: Spell
    {
        if(details.SpellCaster.Energy < T.EnergyCost) 
            throw new InsufficientEnergyException();
    
        // ...
    }
    
    var spell = SpellFactory.Hang<Rotation>();
    
  3. Если это звучит как слишком много работы, рассмотрите дешевый выход — это динамический тип, которому вы можете назначать все, что вам нравится, и опрашивать все, что нужно вашим перегрузкам конструктора.

В любом случае я подозреваю, что полиморфные ответы всегда будут лучше. Я всегда рекомендую решения, основанные на сильных сторонах языка и фреймворка: строго типизированные, объектно-ориентированные, читабельные, простые.

Я предполагаю, что вы на правильном пути или, по крайней мере, рассматриваете его; вы снижаете дублирование и зависимости, перегружая конструкторы (или какой-либо метод «сделать»), повышая при этом читаемость, если вы строго или слабо вводите какую-то структуру параметров в фабрику.

person clarkitect    schedule 19.05.2014
comment
Итак, давайте посмотрим, пойму ли я это... 1. Значит, вы говорите, что я соответствую нескольким интерфейсам, а не только абстрактному классу WCSpell, который я использую сейчас? 2.var является универсальным типом? но как это решает мои проблемы с зависимостями? не могли бы вы подробнее рассказать об этом? 3. Это вариант, который я только что рассматривал, если мой abstractFactory будет выглядеть как public abstract WCSpell get(SpellSubType sSubType,SpellCard sCard, Dictionary<int,object>);, и каждое заклинание будет ожидать, что словарь будет содержать значения, которые ему нужны для ключей, которые он знает/имеет. - person Genhain; 19.05.2014
comment
Var не является общим типом, это просто сокращение. Общая типизация позволяет вам определить метод, например InstantiateSpell<T>(...), где вы можете настроить его поведение в зависимости от типа T. ссылка msdn для этого. Я подробнее остановлюсь в своем ответе, а не в комментариях. Один момент. - person clarkitect; 19.05.2014
comment
Ах, верно, для 1. invocationinfo, кажется, суть этого решения в том, что оно будет содержать всю необходимую информацию для любого заклинания, которое мне нужно, а не иметь все зависимости в конструкторе. Я также могу добавить к этому, поскольку некоторые требования к заклинаниям меняются? в этом случае он не слишком отличается от 3. и 2. опять же, все дело в invocationinfo. Я так понимаю, что вы пытаетесь объяснить, что вы передаете зависимости для заклинания, когда вы их разыгрываете, поэтому сохраняя методы создания фабрики, все же ослабляя конструкцию? - person Genhain; 19.05.2014
comment
Да, точно. И вы можете дополнять его всякий раз, когда вам нужно. Старые заклинания просто не будут искать этих членов. - person clarkitect; 19.05.2014