C# 如何修复此工厂模型的不一致性?

C# 如何修复此工厂模型的不一致性?,c#,design-patterns,factory,factory-pattern,C#,Design Patterns,Factory,Factory Pattern,也许这个标题毫无意义。我在创建工厂,其中一个是抽象的。摘要包含一个随机变量,并且CanConfigureXLevel。这些默认值为false(我的意思是,不可用),但如果您想拥有它,只需将其改写为true即可 public abstract class ProblemFactory { protected Random Random = new Random(); public abstract IProblem Generate(); public virt

也许这个标题毫无意义。我在创建工厂,其中一个是抽象的。摘要包含一个随机变量,并且
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();
} 
二元问题的具体类(生成加法)

公共类二进制ProblemFactory:ProblemFactory
{ 
私有边界_bound1;
私有边界_bound2;
公共二进制ProblemFactory(级别)
{ 
// ... 
} 
public override IProblem Generate()
{ 
int x=random.Next(_bound1.Min,_bound1.Max);
int y=random.Next(_bound2.Min,_bound2.Max);
运算符op=运算符。加法;
返回新的二进制问题(x,y,运算符,答案);
}
公共覆盖布尔CanConfigureMediumLevel()
{
返回true;
}
公共覆盖布尔CanConfigureHardLevel()
{
返回true;
}
受保护的覆盖无效配置EasyLevel()
{
// ...
}
受保护的覆盖无效配置MediumLevel()
{
这个._bound1=新的界限(10100);
这个._bound2=新边界(10100);
}
受保护的覆盖无效配置硬级别()
{
此._bound1=新边界(1001000);
此._bound2=新边界(1001000);
}
}
Bound只是一个包含最小和最大泛型值的类

请记住,BinaryProblemFactory包含一个随机属性。我正在创建几个数学问题,上面是加法问题,我还将创建时间表(非常类似于二进制问题,但这是针对乘法和不同的边界)

我的意思是,每个具体工厂都需要一个UTIL或对象容器来设置程序。Binary和TimeTablesFactory需要两个绑定属性

我的主要问题是..我需要在一个列表中显示哪些级别可用(只有两个以上,中等和硬)。我想如果我们维护一个字典,我可以覆盖
CanConfigureXLevel
,其中键将是一个级别枚举,值将是条件(绑定对象)


但是我不确定应该删除什么。我需要一点帮助。

我认为您的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>
/// 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; }
}
因此,这一切归结为你需要如何消费所有这些。 这篇文章的寓意是,没有必要在抽象工厂中尝试和强制配置如果您只需要配置,工厂应该做的就是创建实例并知道要创建什么类型,仅此而已,但您可能并不真正需要它:)


祝你好运

很好的回答:)只有一个警告。。注意业务逻辑类中的新操作符。您可能的改进使代码更易于使用(您不必通过配置对象),但也使单元测试更加困难。依赖项注入将有助于构建对象链,并减少耦合。看看确实,好的一点@WouterdeKort,这确实可以进一步改进,在适当的情况下使用依赖注入和IOC框架。话虽如此,这个工厂模式是可测试的,因为如果选择工厂方法,它可以在
RegisterProblem
中接受mock作为注册的IProblem类型,但是可以理解的是,简单地实例化
新的二进制问题
s会导致使用它们的代码中出现可测试性问题。回答得很好。我喜欢你的设计,设计改进了很多!这就是为什么我认为这个答案是最好的。只有一个疑问,如果我们需要在
BinaryLevelConfiguration
中添加几个
ProblemConfiguration
,会有多困难。我的意思是,在
GetHardLevelConfiguration()
中支持类似于
IEnumerable
的东西。您认为添加新类ListProblemFactory来添加新功能更好吗?我的意思是,如果我想添加两个配置,必须依次生成配置问题。添加额外的配置非常容易。例如,您可以添加:
AddConfigurableLevel(Level.VeryHard,GetVeryHardLevelConfiguration())并添加适当的方法。我不认为我会将多个配置添加到同一级别,如果配置中有不同之处,我只会添加更多级别以反映这些差异,除非您正在尝试实现其他一些功能?
/// <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);