如何使用依赖于模型属性的接口来设计领域模型
我有一个域模型(参见下面的示例),它有几个接口,这些接口是根据模型本身内的属性值确定的。虽然下面的代码“有效”,但感觉不太对劲。我确信可能有一种更好的方法,但我还没有想出一种方法。我非常有兴趣通过一些替代方法获得反馈。
public interface IPickListGenerator
{
void Execute();
}
public class SportsPicklistGenerator : IPickListGenerator
{
public void Execute()
{
// Do something
}
}
public class EntertainmentPicklistGenerator : IPickListGenerator
{
public void Execute()
{
// Do something
}
}
public interface IQuestionIsAnswerableDeterminer
{
void Execute();
}
public class GameQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
public void Execute()
{
// Do something
}
}
public class RoundQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
public void Execute()
{
// Do something
}
}
public class Pool
{
public enum PoolType
{
Sports,
Entertainment
}
public enum DeadlineType
{
Game,
Round
}
private IPickListGenerator mPickListGenerator = null;
private IQuestionIsAnswerableDeterminer mQuestionIsAnswerableDeterminer = null;
public PoolType Type { get; set; }
public DeadlineType Deadline { get; set; }
public IQuestionIsAnswerableDeterminer IsQuestionAnswerableDeterminer
{
get
{
if (mQuestionIsAnswerableDeterminer == null) SetPoolSpecificInterfaces();
return mQuestionIsAnswerableDeterminer;
}
}
public IPickListGenerator PickListGenerator
{
get
{
if (mPickListGenerator == null) SetPoolSpecificInterfaces();
return mPickListGenerator;
}
}
private void SetPoolSpecificInterfaces()
{
switch (Type)
{
case Pool.PoolType.Sports:
mPickListGenerator = new SportsPicklistGenerator();
break;
case Pool.PoolType.Entertainment:
mPickListGenerator = new EntertainmentPicklistGenerator();
break;
}
switch (Deadline)
{
case Pool.DeadlineType.Game:
mQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer();
break;
case Pool.DeadlineType.Round:
mQuestionIsAnswerableDeterminer = new RoundQuestionIsAnswerableDeterminer();
break;
}
}
}
// Example usages:
var pool = new Pool{ Type = Pool.PoolType.Sports, Deadline = Pool.DeadLineType.Game};
pool.IsQuestionAnswerableDeterminer.Execute();
pool.PickListGenerator.Execute();
I have a domain model (see example below) that has several interfaces that are determined based upon the value of properties within the model itself. While the code below "works" it doesn't feel right. I am sure that there is probably a better approach, but I haven't been able to come up with one. I would be very interested in getting feedback with some alternate approaches.
public interface IPickListGenerator
{
void Execute();
}
public class SportsPicklistGenerator : IPickListGenerator
{
public void Execute()
{
// Do something
}
}
public class EntertainmentPicklistGenerator : IPickListGenerator
{
public void Execute()
{
// Do something
}
}
public interface IQuestionIsAnswerableDeterminer
{
void Execute();
}
public class GameQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
public void Execute()
{
// Do something
}
}
public class RoundQuestionIsAnswerableDeterminer : IQuestionIsAnswerableDeterminer
{
public void Execute()
{
// Do something
}
}
public class Pool
{
public enum PoolType
{
Sports,
Entertainment
}
public enum DeadlineType
{
Game,
Round
}
private IPickListGenerator mPickListGenerator = null;
private IQuestionIsAnswerableDeterminer mQuestionIsAnswerableDeterminer = null;
public PoolType Type { get; set; }
public DeadlineType Deadline { get; set; }
public IQuestionIsAnswerableDeterminer IsQuestionAnswerableDeterminer
{
get
{
if (mQuestionIsAnswerableDeterminer == null) SetPoolSpecificInterfaces();
return mQuestionIsAnswerableDeterminer;
}
}
public IPickListGenerator PickListGenerator
{
get
{
if (mPickListGenerator == null) SetPoolSpecificInterfaces();
return mPickListGenerator;
}
}
private void SetPoolSpecificInterfaces()
{
switch (Type)
{
case Pool.PoolType.Sports:
mPickListGenerator = new SportsPicklistGenerator();
break;
case Pool.PoolType.Entertainment:
mPickListGenerator = new EntertainmentPicklistGenerator();
break;
}
switch (Deadline)
{
case Pool.DeadlineType.Game:
mQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer();
break;
case Pool.DeadlineType.Round:
mQuestionIsAnswerableDeterminer = new RoundQuestionIsAnswerableDeterminer();
break;
}
}
}
// Example usages:
var pool = new Pool{ Type = Pool.PoolType.Sports, Deadline = Pool.DeadLineType.Game};
pool.IsQuestionAnswerableDeterminer.Execute();
pool.PickListGenerator.Execute();
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
除了潜在的逻辑不一致(随后设置
Type
或Deadline
将导致将确定器和生成器的“不正确”值提供给用户)之外,我不认为从更大的角度看设计有什么问题。这与许多工厂模式的工作方式非常相似:由多个内部具体类实现的外部接口(或基类),其正确的实例由某种判别值确定。除非构建成本高昂(并且并不总是必要),否则我建议转向
Type
和Deadline
更智能的属性,在它们的中设置适当的接口实现变量set
块,而不是像现在一样在其他属性中延迟加载它们。如果构造很昂贵,我仍然会切换到
Type
和Deadline
更智能的属性并清除(并进行任何必要的清理)之前可能已设置的值。Other than potential logical inconsistencies (subsequently setting
Type
orDeadline
will result in the "incorrect" value for your determiner and generator being supplied back to the user), I don't see anything wrong with the design from a larger perspective. This is very similar to the way a lot of factory patterns work: an external interface (or base class) that's implemented by multiple internal concrete classes, the correct instance of which is determined by some sort of discriminating value.Unless the construction is expensive (and won't always be necessary), I'd suggest moving to smarter properties for
Type
andDeadline
that set the appropriate interface implementation variables in theirset
blocks rather than loading them lazily in the other properties as you do now.If the construction is expensive, I would still switch to smarter properties for
Type
andDeadline
and clear out (and do any necessary clean up on) the values that might have been previously set.您可能需要从代码中删除依赖项。代码中的过多存在有两种方式:
最后,我在与 Adam 的对话中得到了这一点,这消除了异常抛出,不需要两个 switch 语句,两个枚举,同时保留池闭包并消除代码中可能的错误。对于每个 switch 语句(每个接口实现对),您必须正确实现其自己的静态方法实例化 Pool 类:
--- 该帖子的历史,导致此解决方案:
您可以通过简化代码来改进代码,因为在您的实现上,Pool类仅限于枚举,如果是的话,Pool类是不可扩展的。当新的需求和接口实现出现时,您可能必须一次又一次地重写 SetPoolSpecificInterfaces()。因此,当前设计的最弱点是特定的 SetPoolSpecificInterfaces() 方法,如果是的话,则不是可扩展的 Pool 类。
如果这就是您所需要的,那么接口并不是真正需要的。在其他情况下,您将不依赖于接口实现的数量(实际上是由相应的枚举修复的),因此,它可能可以简化为以下代码(我修复了问题),消除了这些限制:
您可能可以停止在这里,但是如果您总是在 ExecutePickListGenerator() 调用之前调用 ExecuteIsQuestionIsAnswerableDeterminer() 该怎么办?那么你可以重构代码如下:
如果是这样,你可以编写如下所示的内容:
正如@Adam Robinson所说,它改变了 Pool 的一些逻辑(要解决这个问题,你可以使用 close Pool 实现),所以你可以在这个中修复它方式,引入新接口 IPool,向 Pool 类添加私有构造函数和静态逻辑实例化,这将完全消除 swith 语句的需要:
这是由 Arnis 编辑的:
You probably need to remove the dependencies from your code. Excessiveness from your code exists is in 2 ways:
Finally, I've got this, in dialog with Adam, which eliminates exception throws, the need of two switch statement, two enumerations, whilst preserving pool closures and elimination possible errors in code. For each switch statement (each interface implementation pair), you have to implement its own static method instantiating Pool class properly:
--- History of that post, leading to this solution:
You can improve your code by simplifying the code, because in your implementation, Pool class is limited to enumerations, and if so, the Pool class is not extensible. You probably will have to rewrite SetPoolSpecificInterfaces() again and again, when new requirements and interface implementation comes into view. So the weakest point of the current design is that particular SetPoolSpecificInterfaces() method and if so, not extensible Pool class.
If that is what you required, than interfaces is not really needed. In other case, you will not be dependent upon number of interface implementations (which is actually fixed by the corresponding enum) so, it probably can be be simplified to following code (i fixed the issues), removing these constraints:
You probably can stop here, but what if you will always call ExecuteIsQuestionIsAnswerableDeterminer() before ExecutePickListGenerator() call? then you can refacor code as follows:
If so, you can write something looks like this:
As @Adam Robinson said, it changes some logic of Pool (to fix this you can use close Pool implementation), so you can fix it in this way, introducing new interface, IPool, adding private constructor to Pool class and static logic instantiation, that will eliminate the need of a swith statement completely:
This was edited by Arnis: