Как исправить несоответствие этой заводской модели? - PullRequest
0 голосов
/ 27 января 2012

Возможно, название не имеет смысла.Я создаю фабрики, одна из них абстрактная.Аннотация содержит случайную величину и CanConfigureXLevel.Этим по умолчанию является false (я имею в виду, недоступно), но если вы хотите, чтобы оно было, просто измените его на true.

public abstract class ProblemFactory 
{ 
    protected Random Random = new Random(); 

    public abstract IProblem Generate(); 

    public virtual bool CanConfigureEasyLevel()
    {
        return false;
    }

    public virtual bool CanConfigureMediumLevel()
    {
        return false;
    }

    public virtual bool CanConfigureHardLevel()
    {
        return false;
    }

    protected abstract void ConfigureEasyLevel();
    protected abstract void ConfigureMediumLevel();
    protected abstract void ConfigureHardLevel();
} 

Конкретный класс для бинарных задач (генерировать дополнения)

public class BinaryProblemFactory : ProblemFactory 
{ 

    private Bound<int> _bound1; 
    private Bound<int> _bound2; 

    public BinaryProblemFactory(Level level) 
    { 
        // ... 
    } 

    public override IProblem Generate() 
    { 
        int x = random.Next(_bound1.Min, _bound1.Max); 
        int y = random.Next(_bound2.Min, _bound2.Max); 

        Operators op = Operators.Addition;
        return new BinaryProblem(x, y, operator, answer); 
    }

    public override bool CanConfigureMediumLevel()
    {
        return true;
    }

    public override bool CanConfigureHardLevel()
    {
        return true;
    }

    protected override void ConfigureEasyLevel()
    {
        // ...
    }

    protected override void ConfigureMediumLevel()
    {
        this._bound1 = new Bound<int>(10, 100); 
        this._bound2 = new Bound<int>(10, 100); 
    }

    protected override void ConfigureHardLevel()
    {
        this._bound1 = new Bound<int>(100, 1000); 
        this._bound2 = new Bound<int>(100, 1000); 
    }
}

Bound - это просто класс, который содержит универсальное значение Min и Max.

Помните, что BinaryProblemFactory содержит свойство Random.Я создаю несколько задач по математике, выше для задач сложения, также я создам для таблиц времени (очень похоже на BinaryProblem, но это для умножения и различных границ.

Я имею в виду, каждая конкретная фабрика нуждаетсяКонтейнер утилит или объектов для установки программы. Binary и TimesTablesFactory нужны два связанных свойства.

Моя основная проблема заключается в том, что мне нужно показать список доступных уровней (выше двух, средних и сложных).Наверное, я могу исправить это, переопределив CanConfigureXLevel, если мы будем поддерживать словарь, где ключом будет перечисление Level, а значением будут условия (связанные объекты).

Но я не уверенчто я должен удалить. Мне нужно немного помощи.

1 Ответ

2 голосов
/ 03 февраля 2012

Я думаю, что ваш ProblemFactory потенциально пытается сделать слишком много, фабрика должна нести ответственность только за создание экземпляров и , зная, какие типы экземпляров создавать, без дополнительных затрат на знаниео конфигурациях.

Имея это в виду, вот как я бы подошел к проблеме:

/// <summary>
/// Each class that can generate a problem should accept a problem configuration
/// </summary>
public class BinaryProblem : IProblem
{
    public BinaryProblem (ProblemConfiguration configuration)
    {
        // sample code, this is where you generate your problem, based on the configuration of the problem
        X = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
        Y = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
        Answer = X + Y; 
    }

    public int X { get; private set; }
    public int Y { get; private set; }
    public int Answer { get; private set; }
}

Для этого нам понадобится класс конфигурации проблемы

/// <summary>
/// A problem configuration class
/// </summary>
public class ProblemConfiguration
{
    public int MinValue { get; set; }
    public int MaxValue { get; set; }
    public Operator Operator { get; set; }
}

Я бы тожевыделенный класс для обработки конфигурации уровней и удаления ее из класса фабрики.

/// <summary>
/// The abstract level configuration allows descendent classes to configure themselves
/// </summary>
public abstract class LevelConfiguration
{
    protected Random Random = new Random();
    private Dictionary<Level, ProblemConfiguration> _configurableLevels = new Dictionary<Level, ProblemConfiguration>();

    /// <summary>
    /// Adds a configurable level.
    /// </summary>
    /// <param name="level">The level to add.</param>
    /// <param name="problemConfiguration">The problem configuration.</param>
    protected void AddConfigurableLevel(Level level, ProblemConfiguration problemConfiguration)
    {
        _configurableLevels.Add(level, problemConfiguration);
    }

    /// <summary>
    /// Removes a configurable level.
    /// </summary>
    /// <param name="level">The level to remove.</param>
    protected void RemoveConfigurableLevel(Level level)
    {
        _configurableLevels.Remove(level);
    }

    /// <summary>
    /// Returns all the configurable levels.
    /// </summary>
    public IEnumerable<Level> GetConfigurableLevels()
    {
        return _configurableLevels.Keys;
    }

    /// <summary>
    /// Gets the problem configuration for the specified level
    /// </summary>
    /// <param name="level">The level.</param>
    public ProblemConfiguration GetProblemConfiguration(Level level)
    {
        return _configurableLevels[level];
    }
}

Это позволит двоичной конфигурации выглядеть примерно так:

/// <summary>
/// Contains level configuration for Binary problems
/// </summary>
public class BinaryLevelConfiguration : LevelConfiguration
{
    public BinaryLevelConfiguration()
    {
        AddConfigurableLevel(Level.Easy, GetEasyLevelConfiguration());
        AddConfigurableLevel(Level.Medium, GetMediumLevelConfiguration());
        AddConfigurableLevel(Level.Hard, GetHardLevelConfiguration());
    }

    /// <summary>
    /// Gets the hard level configuration.
    /// </summary>
    /// <returns></returns>
    private ProblemConfiguration GetHardLevelConfiguration()
    {
        return new ProblemConfiguration
        {
            MinValue = 100,
            MaxValue = 1000,
            Operator = Operator.Addition
        };
    }

    /// <summary>
    /// Gets the medium level configuration.
    /// </summary>
    /// <returns></returns>
    private ProblemConfiguration GetMediumLevelConfiguration()
    {
        return new ProblemConfiguration
        {
            MinValue = 10,
            MaxValue = 100,
            Operator = Operator.Addition
        };
    }

    /// <summary>
    /// Gets the easy level configuration.
    /// </summary>
    /// <returns></returns>
    private ProblemConfiguration GetEasyLevelConfiguration()
    {
        return new ProblemConfiguration
        {
            MinValue = 1,
            MaxValue = 10,
            Operator = Operator.Addition
        };
    }


}

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

/// <summary>
/// The only responsibility of the factory is to create instances of Problems and know what kind of problems it can create, 
/// it should not know about configuration
/// </summary>
public class ProblemFactory
{

    private Dictionary<Type, Func<Level, IProblem>> _registeredProblemTypes; // this associates each type with a factory function

    /// <summary>
    /// Initializes a new instance of the <see cref="ProblemFactory"/> class.
    /// </summary>
    public ProblemFactory()
    {
        _registeredProblemTypes = new Dictionary<Type, Func<Level, IProblem>>();
    }

    /// <summary>
    /// Registers a problem factory function to it's associated type
    /// </summary>
    /// <typeparam name="T">The Type of problem to register</typeparam>
    /// <param name="factoryFunction">The factory function.</param>
    public void RegisterProblem<T>(Func<Level, IProblem> factoryFunction)
    {
        _registeredProblemTypes.Add(typeof(T), factoryFunction);
    }

    /// <summary>
    /// Generates the problem based on the type parameter and invokes the associated factory function by providing some problem configuration
    /// </summary>
    /// <typeparam name="T">The type of problem to generate</typeparam>
    /// <param name="problemConfiguration">The problem configuration.</param>
    /// <returns></returns>
    public IProblem GenerateProblem<T>(Level level) where T: IProblem
    {
        // some extra safety checks can go here, but this should be the essense of a factory,
        // the only responsibility is to create instances of Problems and know what kind of problems it can create
        return _registeredProblemTypes[typeof(T)](level); 
    }
}

Тогда вот как вы можете использовать все это

class Program
{
    static void Main(string[] args)
    {
        ProblemFactory problemFactory = new ProblemFactory();
        BinaryLevelConfiguration binaryLevelConfig = new BinaryLevelConfiguration();


        // register your factory functions
        problemFactory.RegisterProblem<BinaryProblem>((level) => new BinaryProblem(binaryLevelConfig.GetProblemConfiguration(level)));

        // consume them
        IProblem problem1 = problemFactory.GenerateProblem<BinaryProblem>(Level.Easy);
        IProblem problem2 = problemFactory.GenerateProblem<BinaryProblem>(Level.Hard);
    }
}

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

IProblem problem3 = new BinaryProblem(binaryLevelConfig.GetProblemConfiguration(Level.Easy)); 

Возможные улучшения

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

/// <summary>
/// Each class that can generate a problem should accept a level configuration
/// </summary>
public class BinaryProblem : IProblem
{

    private static BinaryLevelConfiguration _levelConfiguration = new BinaryLevelConfiguration();

    public BinaryProblem (Level level)
    {
        ProblemConfiguration configuration = _levelConfiguration.GetProblemConfiguration(level);
        // sample code, this is where you generate your problem, based on the configuration of the problem
        X = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
        Y = new Random().Next(configuration.MaxValue + configuration.MinValue) - configuration.MinValue;
        Answer = X + Y; 
    }

    public int X { get; private set; }
    public int Y { get; private set; }
    public int Answer { get; private set; }
}

Тогда все, что вам нужно сделать, это:

IProblem problem4 = new BinaryProblem(Level.Easy);

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

Удачи!

...