如何使用依赖于模型属性的接口设计域模型

时间:2011-01-23 00:21:43

标签: c# .net domain-driven-design

我有一个域模型(参见下面的示例),它有几个接口,这些接口是根据模型本身的属性值确定的。虽然下面的代码“有效”,但感觉不对。我确信可能有更好的方法,但我无法想出一个。我会非常有兴趣通过一些替代方法获得反馈。

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();

2 个答案:

答案 0 :(得分:4)

除了潜在的逻辑不一致(随后设置TypeDeadline将导致确定器和生成器的“不正确”值被提供给用户),我没有看到任何错误从更大的角度来看设计。这与许多工厂模式的工作方式非常相似:由多个内部具体类实现的外部接口(或基类),其正确的实例由某种区别值决定。

除非构造昂贵(并且并非总是必要),否则我建议转移到TypeDeadline的更智能的属性,在{{1}中设置适当的接口实现变量阻止而不是像现在一样懒惰地将它们加载到其他属性中。

如果构造 昂贵,我仍然会切换到setType的更智能的属性,并清除(并进行必要的清理)可能以前设定过。

答案 1 :(得分:1)

您可能需要从代码中删除依赖项。您的代码存在的过多有两种方式:

  • 你有过多的代码(接口)
  • 你有过多的限制(枚举)

最后,我在与Adam的对话中得到了这个,它消除了异常抛出,需要两个switch语句,两个枚举,同时保留了池关闭并消除了代码中可能出现的错误。对于每个switch语句(每个接口实现对),您必须实现自己的静态方法,正确实例化Pool类:

public interface IPool
{
    void Execute();
}

public class Pool : IPool
{
    private Pool() { }

    public IPickListGenerator PickListGenerator { set; private get; }
    public IQuestionIsAnswerableDeterminer IsQuestionIsAnswerableDeterminer { set; private get; }

    public static IPool GetSportsGame() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
        };
    }
    public static IPool GetSportsEntertainment() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new EntertainmentPicklistGenerator()
        };
    }
    public void Execute()
    {
        IsQuestionIsAnswerableDeterminer.Execute();
        PickListGenerator.Execute();
    }
}

---该帖子的历史,导致这个解决方案:

您可以通过简化代码来改进代码,因为在您的实现中,Pool类仅限于枚举,如果是,则Pool类不可扩展。当新的需求和接口实现进入视图时,您可能必须一次又一次地重写SetPoolSpecificInterfaces()。因此,当前设计的最薄弱点是特定的SetPoolSpecificInterfaces()方法,如果是这样,则不是可扩展的Pool类。

如果这是您所需要的,那么实际上并不需要接口。在其他情况下,您将不依赖于接口实现的数量(实际上由相应的枚举修复),因此,它可能可以简化为以下代码(我修复了问题),删除了这些约束:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Example usages:
            var pool = new Pool
            {
                PickListGenerator = new SportsPicklistGenerator(),
                IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
            };
            pool.ExecuteIsQuestionIsAnswerableDeterminer();
            pool.ExecutePickListGenerator();
        }
    }

    public class Pool
    {
        public IPickListGenerator PickListGenerator { set; private get; }
        public IQuestionIsAnswerableDeterminer
            IsQuestionIsAnswerableDeterminer { set; private get; }

        public void ExecuteIsQuestionIsAnswerableDeterminer()
        {
            IsQuestionIsAnswerableDeterminer.Execute();
        }

        public void ExecutePickListGenerator()
        {
            PickListGenerator.Execute();
        }
    }
}

你可能会在这里停下来,但是如果你在ExecutePickListGenerator()调用之前总是调用ExecuteIsQuestionIsAnswerableDeterminer()怎么办?然后您可以按如下方式重新编码代码:

    public class Pool
    {
        public IPickListGenerator PickListGenerator { set; private get; }
        public IQuestionIsAnswerableDeterminer 
            IsQuestionIsAnswerableDeterminer { set; private get; }

        public void Execute()
        {
            // improvements, seen by Arnis
            if (IsQuestionIsAnswerableDeterminer == null)
            {
                thrown new ArgumentException("IsQuestionIsAnswerableDeterminer");
            }
            if (PickListGenerator == null)
            {
                thrown new ArgumentException("PickListGenerator");
            }
            IsQuestionIsAnswerableDeterminer.Execute();
            PickListGenerator.Execute();
        }
    }

如果是这样,你可以写下这样的东西:

var pool = new Pool
{
    PickListGenerator = new SportsPicklistGenerator(),
    IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
};
pool.Execute();

// reuse the same class logic in Pool class in Execute method
pool.PickListGenerator = new GamePickListGenerator();
pool.Execute();

// reuse the same class logic in Pool class in Execute method
pool.IsQuestionIsAnswerableDeterminer = new RoundQuestionIsAnswerableDeterminer();
pool.Execute();

正如@Adam Robinson所说,它改变了Pool的一些逻辑(为了解决这个问题你可以使用close pool实现),所以你可以用这种方式修复它,引入新的接口,IPool,为Pool类添加私有构造函数和静态逻辑实例化,将完全消除swith语句的需要:

public interface IPool
{
    void Execute();
}

public class Pool : IPool
{
    private Pool() { }

    public IPickListGenerator PickListGenerator { set; private get; }
    public IQuestionIsAnswerableDeterminer IsQuestionIsAnswerableDeterminer { set; private get; }

    public static IPool GetSportsGame() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new GameQuestionIsAnswerableDeterminer()
        };
    }
    public static IPool GetSportsEntertainment() 
    {
        return new Pool
        {
            PickListGenerator = new SportsPicklistGenerator(),
            IsQuestionIsAnswerableDeterminer = new EntertainmentPicklistGenerator()
        };
    }
    public void Execute()
    {
        IsQuestionIsAnswerableDeterminer.Execute();
        PickListGenerator.Execute();
    }
}

这是由Arnis编辑的:

public class Pool{
  private readonly IPickListGenerator _generator;
  private readonly IQuestionIsAnswerableDeterminer _determiner;
  public Pool(IPickListGenerator generator, 
    IQuestionIsAnswerableDeterminer determiner){

    if(determiner==null||generator==null)
      throw new ArgumentNullException();
    _generator=generator;
    _determiner=determiner;
  }

  public void Execute(){
    _determiner.Execute();
    _generator.Execute();
  }
}

var generator=new SportsPicklistGenerator();
var determiner=new GameQuestionIsAnswerableDeterminer();
var pool = new Pool(generator, determiner);
pool.Execute();