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

发布于 2024-10-14 02:50:11 字数 2653 浏览 6 评论 0原文

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

天暗了我发光 2024-10-21 02:50:11

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

除非构建成本高昂(并且并不总是必要),否则我建议转向 TypeDeadline 更智能的属性,在它们的中设置适当的接口实现变量set 块,而不是像现在一样在其他属性中延迟加载它们。

如果构造昂贵,我仍然会切换到TypeDeadline更智能的属性并清除(并进行任何必要的清理)之前可能已设置的值。

Other than potential logical inconsistencies (subsequently setting Type or Deadline 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 and Deadline that set the appropriate interface implementation variables in their set 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 and Deadline and clear out (and do any necessary clean up on) the values that might have been previously set.

难如初 2024-10-21 02:50:11

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

  • 您有过多的代码(接口)
  • 您有过多的约束(枚举)

最后,我在与 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();

You probably need to remove the dependencies from your code. Excessiveness from your code exists is in 2 ways:

  • you have an excessive code (interfaces)
  • you have an excessive constraints (enums)

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:

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

--- 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:

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

You probably can stop here, but what if you will always call ExecuteIsQuestionIsAnswerableDeterminer() before ExecutePickListGenerator() call? then you can refacor code as follows:

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

If so, you can write something looks like this:

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

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:

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

This was edited by 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();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文