【问题标题】:How to fix an inconsistency of this factory model?如何解决此工厂模型的不一致问题?
【发布时间】:2012-01-27 19:07:00
【问题描述】:

也许标题没有意义。我正在创建工厂,其中之一是抽象的。摘要包含一个随机变量和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 枚举,值将是条件(绑定对象)。

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

【问题讨论】:

    标签: c# design-patterns factory factory-pattern


    【解决方案1】:

    我认为您的 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);
    

    所以这一切都归结为你需要如何消费这一切。 这篇文章的寓意是,如果您只需要配置,则无需尝试在抽象工厂中进行配置,工厂应该做的就是创建实例并知道要创建什么类型,仅此而已,但您可以不是真的需要它:)

    祝你好运!

    【讨论】:

    • 不错的答案 :) 只是一个警告.. 注意业务逻辑类中的新运算符。您可能的改进使您的代码更易于使用(您不必传递配置对象),但它也使单元测试更难。依赖注入将有助于构建您的对象链并减少一切耦合。看看misko.hevery.com/code-reviewers-guide
    • 确实,好点@WouterdeKort,这确实可以进一步改进以在适当的情况下使用依赖注入和IOC框架。话虽如此,这个工厂模式是可测试的,因为它可以在RegisterProblem 中接受模拟作为注册的 IProblem 类型,如果选择了工厂方法,但可以理解的是,简单地实例化 new BinaryProblems 会导致使用它们的代码中的可测试性问题。
    • 优秀的答案。我喜欢你的设计,设计改进了很多!这就是为什么我将此答案标记为最佳的原因。只是一个疑问,如果我们需要在BinaryLevelConfiguration 中添加几个ProblemConfiguration 会有多困难。我的意思是,在GetHardLevelConfiguration() 中支持IEnumerable&lt;ProblemConfiguration&gt; 之类的东西。
    • 您认为添加一个新类 ListProblemFactory 来添加新功能会更好吗?我的意思是,如果我想添加两个配置,则必须生成问题,然后再选择配置。
    • 添加额外的配置非常容易。例如,您可以在构造函数中添加:AddConfigurableLevel(Level.VeryHard, GetVeryHardLevelConfiguration()); 并添加适当的方法。我认为我不会将多个配置添加到同一级别,如果配置中有不同之处,我只会添加更多级别来反映这些差异,除非您还有其他想要实现的目标?
    猜你喜欢
    • 2022-01-02
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 2020-01-16
    • 2020-08-17
    • 2021-09-29
    • 1970-01-01
    相关资源
    最近更新 更多