Configuration
作为输入,并返回Problem
作为输出。问题:其中包含需要生成问题的属性。
配置:这是范围参数或条件来生成问题。
工厂:他负责创建新问题。
这里我有工厂接口和标记接口:
public abstract class Problem { }
public abstract class Configuration { }
public interface IProblemFactory
{
Configuration Configuration { get; set; }
Problem CreateProblem();
}
这是工厂的基类,因为我需要
Random
类。我实现该代码的所有类都必须具有相同的种子,因此我有一个静态实例。public abstract class ProblemFactoryBase<P, C> : IProblemFactory
where P : Problem
where C : Configuration
{
private const int DEFAULT_SEED = 100;
protected C _config;
private static Random _random;
public ProblemFactoryBase()
{
if (_random == null) _random = new Random(DEFAULT_SEED);
}
public ProblemFactoryBase(C config)
{
_config = config;
if (_random == null) _random = new Random(DEFAULT_SEED);
}
protected Random Random { get { return _random; } }
public C Configuration
{
get { return _config; }
set { _config = value; }
}
#region IProblemFactory Implementation
Configuration IProblemFactory.Configuration
{
get { return _config; }
set
{
C config = value as C;
if (config == null) throw new InvalidCastException("config");
_config = config;
}
}
protected abstract P CreateProblem(C config);
#endregion
public Problem CreateProblem()
{
if (_config == null) throw new InvalidOperationException("config");
return CreateProblem(_config);
}
public static void SetSeed()
{
_random = new Random(DEFAULT_SEED);
}
}
构造类型时的抽象方法。
当我实现所有这些方法时。例如,用于
ProblemFactoryBase<P, C>
的模块(例如BinaryProblems
)将是:public class BinaryConfiguration : Configuration
{
public Range<int> Range1 { get; set; }
public Range<int> Range2 { get; set; }
public List<Operators> Operators { get; set; }
public BinaryConfiguration(Range<int> range1, Range<int> range2, List<Operators> operators)
{
this.Range1 = range1;
this.Range2 = range2;
this.Operators = operators;
}
public class BinaryProblem : Problem
{
public BinaryProblem(decimal x, decimal y, Operators op, decimal response)
{
this.X = x;
this.Y = y;
this.Response = response;
}
public decimal X { get; private set; }
public decimal Y { get; private set; }
public decimal Response { get; private set; }
}
public enum Operators
{
Addition, Substract, Multiplication, Division
}
最重要的部分是混凝土工厂。该类为基本泛型类指定类型参数。为什么?因为我认为这是实现具体价值的最佳方法,所以我的意思是我现在不必强制转换任何价值。
public class BinaryFactory : ProblemFactoryBase<BinaryProblem, BinaryConfiguration>
{
protected override BinaryProblem CreateProblem(BinaryConfiguration config)
{
var x = GenerateValueInRange(config.Range1);
var y = GenerateValueInRange(config.Range2);
var index = Random.Next(config.Operators.Count);
var op = config.Operators[index];
return new BinaryProblem(x, y, op, x + y);
}
private decimal GenerateValueInRange(Range<int> range)
{
return Random.Next(range.Min, range.Max);
}
}
要实现它是:
BinaryConfiguration configuration = new BinaryConfiguration() {.. }
IProblemFactory factory = new BinaryFactory(configuration);
var a = factory.CreateProblem();
它正在做我喜欢的事情,因为我想将多个工厂排列成一个阵列,但是同时,
2+3
的实现对我来说是好的,因为无需强制转换类型。我还在学习设计模式。也许还有其他方法可以改善它,或者我没有遵循建议。您有何反馈?
#1 楼
可以使用构造函数链接删除此重复的代码public ProblemFactoryBase()
{
if (_random == null) _random = new Random(DEFAULT_SEED);
}
public ProblemFactoryBase(C config)
{
_config = config;
if (_random == null) _random = new Random(DEFAULT_SEED);
}
,并使用花括号
{}
进行美化,就像这样/>
public ProblemFactoryBase()
{
if (_random == null) { _random = new Random(DEFAULT_SEED); }
}
public ProblemFactoryBase(C config)
: this()
{
_config = config;
}
因为类级别常量
DEFAULT_SEED
仅与static Random _random
一起使用,因此应为static readonly
。此外,根据命名准则,应使用PascalCase
大小写进行命名,另请参见:https://stackoverflow.com/a/242549/2655508看起来应该像这样private static readonly int DefaultSeed = 100;
因为
Random _random
和属性Random Random
(不应这样命名,因为您不应该像其类型一样命名属性)不会在构造函数中更改,所以为什么不创建Random _random
一个protected readonly
代替? 好吧,我现在发现了这个
public static void SetSeed()
{
_random = new Random(DEFAULT_SEED);
}
这是IMO错误,至少方法名暗示了一些问题与所做的不同。更改该方法的名称,或让该方法具有方法参数,然后将其用作种子。
关于
#region IProblemFactory Implementation
,请阅读区域是否有反味或代码异味? 如果要保留此区域,则至少应包括
Problem CreateProblem()
方法。顺便说一句,为什么您要对接口进行隐式和显式实现???? 此
有点奇怪。第一个属性仅属于工厂类本身,第二个属性是显式接口实现的属性。
第一个属性需要保留,因为它没有任何帮助,也没有在代码中添加任何值。这样做的唯一好处是,由于显式接口的实现,您无需将实例强制转换为
IProblemFactory
。 在第二个属性设置器中,无需对
C
进行软转换,因为C
已经是Configuration
。 具体的
BinaryFactory
的实现或其示例用法都是有缺陷的,因此无法编译,因为示例用法使用了BinaryFactory
不提供的构造函数。 #2 楼
当将参数传递给public
方法时,您始终需要断言parameter
不是null
,否则您的代码将抛出NullReferenceException
,并且没人会喜欢它们。这条路。 “非书面”约定是默认字母为C
。这并不意味着我们应该使用任何字母! :)如果检查.Net框架命名,则应将参数类型名称重命名为P
,T
。我认为您应该为接口切换TProblem
和TConfig
。通过使用抽象类,可以防止子类继承一个可能更有用的类,尤其是考虑到两个类中都没有属性或方法时,尤其如此。因此,使用接口,每个孩子都可以自由决定是否需要实现另一个类。在
Problem
类中,您可以重复代码。有一个Configuration
方法,该方法包含在构造函数中调用的相同代码。您应该在构造函数中调用ProblemFactoryBase
而不是调用相同的代码。,这种
SetSeed
方法真的有用吗? SetSeed
变量是SetSeed
,因此您只能决定将其设置为另一个值。但是,您无需更改_random
的种子,那么为什么要为此公开private
方法呢?我认为您不需要这种方法。而不是公开_random
,为什么不公开将返回public static
的方法protected Random
。我认为这会更清楚。另外,您确定GetRandomNumber()
不是实施细节吗?为什么Q4312079q会成为您的基类?如果孩子不想使用_random.Next()
怎么办?我认为Random
应该被排除在基类之外,孩子会在需要时创建自己的东西。 > 已经有
Random
实现。这可能会导致一些混乱。我敢肯定,仅公开Random
实现就足够了!如果您担心需要转换Random
而又不知道类型,那么我会问你:什么时候需要从interface
外部转换interface
(由于Configuration
,谁知道Configuration
的类型)? ProblemFactoryBase
消息可能更清晰。毕竟,解释Configuration
并不是很长的信息,而且要比TConfig
清晰得多。我不确定您是否使用最佳设计模式来解决问题。毕竟,
InvalidOperationException
和"the configuration must not be null"
绝对没有共享。 "config"
取决于Problem
。由于Configuration
不是通过工厂创建的,因此我们将从此处删除它开始。毕竟,为什么一个Problem
需要一个Configuration
作为参数? 参数1:如果
Configuration
类或ProblemFactoryBase
不需要Configuration
类,则表示Problem
是实现细节。接口/抽象类不应显示实现细节。为了说明我的观点,这里有一个例子。想象一下,有一天您有两个CreateProblem
的子类。一种使用Configuration
,另一种使用Configuration
。您会在ProblemFactoryBase
中添加Configuration
吗?您是否会为所有子类添加这样的参数? 参数2:如果
IFooBarAlgorithmService
是一个属性,因为您不希望它成为IFooBarAlgorithmService
方法的参数,那么创建一个不需要ProblemFactoryBase
的问题? ProblemFactoryBase
方法将抛出Configuration
。但这真的无效吗?否。子类不想使用CreateProblem
,也不需要使用它,因为它不是Configuration
方法的参数。所以现在您知道为什么
CreateProblem
属性不是最好的选择,让我们看看解决方案:您可能会从
InvalidOperationException
类中完全删除Configuration
属性,因为它是一个实现细节。或将CreateProblem
添加为Configuration
公共方法的参数。基类。现在,我认为
Configuration
应该是方法ProblemFactoryBase
的参数,让我们检查一下所有内容。首先,我们创建一个Configuration
:(注意:我认为
CreateProblem
会比Configuration
更好,它更容易混淆!对于以下示例,我将应用上面所做的评论)public C Configuration
{
get { return _config; }
set { _config = value; }
}
Configuration
非常简单。它指出“实现我的任何东西都应该能够使用配置来创建问题”。这正是我们想要的。始终尝试使您的界面尽可能简洁明了! (您以前的界面很好,这只是一个提示!)接下来,实现的外观是什么样的?并且看起来很像您以前的类,但是请注意,
CreateProblem
和此interface
之间没有基类,并且它没有创建更多代码(我们使用了ProblemConfiguration
变量,该变量可能已经在另一个子类中使用! 现在,您将拥有
Configuration
的多个子类。只有一个入口门可以配置并得到相应的问题,这不是很好吗?哦,是的,这很干净。那就是抽象工厂的所在!考虑一下它是所有“子”工厂的包装类。 !
public interface IProblemFactory<TProblem, TConfig>
where TProblem : IProblem
where TConfig : IProblemConfiguration
{
TProblem CreateProblem(TConfig configuration);
}
繁荣,现在您可以在所有工厂都拥有一个共同的地方。随着时间的流逝,您将不得不添加大量的
interface
。我们可以通过更改接口来解决此问题,这将更改程序结构中的某些内容,但是此解决方案也有缺点。无论如何,我都会向您显示,以便您可以更好地确定哪个适合您的需求。 因此,如果改用我们的
interface
:(注意:在键入时,我意识到
class
应该命名为Random
。毕竟,您已经知道要创建由于返回类型是IProblemFactory
,因此会出现问题) >但是我们在您的if/else
中获得了一个优势:public class BinaryProblemFactory : IProblemFactory<BinaryProblem, BinaryConfiguration>
{
private static readonly Random Random = new Random();
public BinaryProblem CreateProblem(BinaryConfiguration configuration)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
var x = GenerateValueInRange(configuration.Range1);
var y = GenerateValueInRange(configuration.Range2);
var index = Random.Next(configuration.Operators.Count);
var op = configuration.Operators[index];
return new BinaryProblem(x, y, op, x + y);
}
private static decimal GenerateValueInRange(Range<int> range)
{
return Random.Next(range.Min, range.Max);
}
}
我们现在可以使用
interface
代替CreateProblem
的链。老实说,我不是很喜欢第三种解决方案。它涉及反射,这意味着速度较慢且很好……不是很干净。我还是给你看,因为好吧,我不是你的主人。 :p此解决方案使用第一个界面。不要忘了使用反射会松开编译时间检查,这不是一个好的解决方案。在第一个和第二个之间,因为它们都有优点和缺点,但是它们都应该非常牢固! :)
评论
\ $ \ begingroup \ $
艰难的决定,但是您的答案涵盖了更多的领域,并且由于我真的很喜欢我的工厂接口具有Create方法(KISS,对吗?),因此您将其钉牢。恭喜你!
\ $ \ endgroup \ $
– Mathieu Guindon♦
15年12月20日在13:41
\ $ \ begingroup \ $
返回new BinaryProblem(x,y,op,x + y);您将BinaryProblem的响应参数设置为x + y,但这不应该是x op y(在伪代码中)吗?也就是说,现在您总是会给出加法响应,但实际上它也应该使用问题指定的操作。
\ $ \ endgroup \ $
–skiwi
15年12月20日在16:25
\ $ \ begingroup \ $
@skiwi糟糕,我为这种情况复制了OP的代码,但您说对了,有些事情发生了!
\ $ \ endgroup \ $
–IEatBagels
2015年12月20日下午16:26
#3 楼
这似乎不应该处理工厂模式。工厂模式更像是在不暴露实例化逻辑的情况下创建对象。最好也与此一起使用接口(就像您一样)。我认为您应该检查的是解释器模式。非常适合处理表达式(在这种情况下为问题)。
检查以下内容:
http://www.dofactory.com/Patterns/PatternInterpreter.aspx#_self2
#4 楼
对已发表的评论进行了少量补充:private decimal GenerateValueInRange(Range<int> range)
{
return Random.Next(range.Min, range.Max);
}
为什么会混合decimal
和int
?在我看来,如果生成器产生涉及整数的问题,则不应在任何地方使用小数;并且如果它产生涉及小数的问题,则将Range<decimal>
用作界限会更加清楚。我可以为将其包含在Random
中(或围绕Random
的包装器)做一个论点;如果可以找到一种不会导致类型系统出现问题的方法,我可以为将其包含在Range<T>
中做一个论证。并且我可以为将其作为Range<int>
的扩展方法而包含一个参数。但是,如果它是生成器中的私有方法,则最终将其复制粘贴到整个地方。
评论
太简单的2 + 3 =?例。您研究了设计模式并想实现一种,但是为什么呢?我对许多模式的问题是,我看不到要点,它们感觉是虚假的。我几乎无法想象对此有真正的需求。如果眼前的问题足够复杂,我可能已经看到了这种需要,但是学术实例并没有太大的动机。如果您描述了足够的用例以查看模式,那么我可以更加确定地确定Factory Pattern是否是正确的工具。感觉不像一个。通常有替代解决方案。我不强迫您使用代码。之所以这样做,是因为我正在创建一个模块化应用程序,其中每个模块都包含创建问题的方式。我正在描述数以百计的数学问题。
让我建议一条更好的路线。我相信您想使用域特定语言或某种形式的文本解析,这种方式更加灵活。这是一个很酷的论文的示例:math.utah.edu/~hohn/spemhh.pdf和另一个:任意.name / papers / fpf.pdf这是一个F#DSL的示例:social.msdn.microsoft.com/论坛/ zh-CN / fsharpgeneral / thread /…以及:udooz.pressbooks.com/chapter/external-dsl-using-f-sharp-2-0和Clojure:learningclojure.com/2010/02/…fnc prg规则。
我强烈同意列昂尼德的观点。大部分代码是无用的,您正在尝试实现不需要它的模式。我开始写一个答案,但是我发现问题太高了,您会发现您没有有用的基类和泛型,这迫使您知道实现细节正在消失的工厂模式本身。删除所有内容,然后重新开始(从底部开始!您要编写使用这些类的第一个代码,然后转到通用实现)。
您...您实际上写了一个ProblemFactory。您赢得了互联网。