【问题标题】:How to limit certain behavior to a subset of instances of a particular class?如何将某些行为限制为特定类的实例子集?
【发布时间】:2012-05-06 16:43:52
【问题描述】:

我正在为配方应用程序开发域模型并遇到问题。

该应用程序具有多个能够充当成分的实体,其中两个是:ProductRecipe(配方可以是其他配方中的成分)。通常我会将与成分相关的功能封装到每个实体都可以实现的接口中。问题在于,虽然所有 Product 实例都可以是成分,但只有 Recipe 实例的一个子集可以是成分

interface IIngredient
{
    void DoIngredientStuff();
}

class Product : IIngredient
{
    void DoIngredientStuff()
    {
        // all Products are ingredients - do ingredient stuff at will
    }
}

class Recipe : IIngredient
{
    public IEnumerable<IIngredient> Ingredients { get; set; }

    void DoIngredientStuff()
    {
        // not all Recipes are ingredients - this might be an illegal call
    }
}

如何重新构建此模型以支持只有一些配方实例才能充当成分的要求?

【问题讨论】:

  • 要找出好的答案,我们需要了解为什么所有食谱都不能作为配料

标签: c# domain-driven-design data-modeling


【解决方案1】:

另一个选项,如果它在您的类树中为这两种类型的食谱有单独的类。请注意,如果您想通过更多属性来区分对象,则此方法效果不佳。

class Recipe {
    public IEnumerable<IIngredient> Ingredients { get; set; }

}

class UsefulRecipe : Recipe, IIngredient
{
    void DoIngredientStuff()
    {
        // not all Recipes are ingredients - this might be an illegal call
    }
}

【讨论】:

  • SmartRecipe、UsefulRecipe、RecipeWithOnlyFiveInstructions、RecipeThatCanBeOnlyFiveYearsOld、MyGrandmasHalfHundredYearOldRecipeOfExtraTastyPancakesThatWeAteLastNewYearEve
  • 我试图写下我的答案,但它在第二行并且......如果您有更好的表达方式,请随时编辑答案。
【解决方案2】:

对我来说听起来像是一个设计问题。如果您必须开始为 IsIngredient 进行测试,我认为您的设计出了问题。当您遇到另一个特殊情况时会发生什么?然后另一个?你会继续添加特殊的 If 测试或大的 switch 语句吗?这打破了开闭原则。

如何选择组合而不是继承?您可能还想查看策略模式...

真正的核心问题是,Recipe 不应该实现 IIngredient...因为并非所有 Recipes 都实现 IIngredient 行为...

【讨论】:

    【解决方案3】:
    interface IIngredient 
    { 
        bool IsIngredient { get; }
        void DoIngredientStuff(); 
    } 
    
    class Recipe : IIngredient 
    { 
        public IEnumerable<IIngredient> Ingredients { get; set; } 
    
        bool IsIngredient {
           get { return true; // true if it is, false if it isn't }
        }   
    
        void DoIngredientStuff() 
        { 
          if (IsIngredient) {
            // do whatever
          }
        } 
    } 
    

    【讨论】:

    • 这行得通……但它的设计有缺陷且不可扩展。每次需要将新的特殊条件添加到设计中时,都需要进行重构。
    • 我不认为它有缺陷。这是 .NET System.IO.Stream 库使用的方法。有些流支持读取,有些不支持,这是通过布尔属性传达的。就您而言,有些食谱是成分,有些则不是,这是表达这一点的最简单方法之一。此外,如果要持久化这些实体,更容易避免使用多态方法,而是选择在每个配方中存储一个位字段以指示它是否可以用作成分。
    • 这是有缺陷的。违反了最小惊讶原则(en.wikipedia.org/wiki/Principle_of_least_astonishment)。如果它是一种成分,它必须能够做成分材料。否则 - 强制执行它的接口有什么意义?
    • @eulerfx .NET 框架充满了丑陋的代码yourlogicalfallacyis.com/appeal-to-authority
    • 我不认为引用流库作为例子是一个谬误。但是,如果按照这个标记,你不认为你的两个参考都是诉诸权威谬误的例子吗?
    【解决方案4】:

    我可能会使用组合来创建一个CompositeRecipe,它可以组合 2 个(也许更多)食谱。没有所有食谱都可以用作新食谱的基础的原因是什么?您始终可以向 suggest 添加一个布尔属性,表明该配方是一个完整的配方 (IsCompleteRecipe),然后取决于您如何构建应用程序和应用程序逻辑来确定该配方是否应该可与他人结合。

    【讨论】:

    • 编写食谱无法解决并非所有食谱都是配料的问题
    【解决方案5】:

    如果只有一些食谱是成分,那么它似乎是一个经典的继承案例,IIngredient 在子类上实现:

    class Product : IIngredient
    {
        void DoIngredientStuff();
    }
    
    class Recipe
    {
        public IEnumerable<IIngredient> Ingredients { get; set; }
    }
    
    class IngredientRecipe : Recipe, IIngredient
    {
        void DoIngredientStuff();
    }
    

    【讨论】:

    • 这不是关于配料的食谱,而是关于一些可以作为配料的食谱。有很大的不同。
    • 在这个设计中,IngredientRecipe 就是这样:一个可以作为配料的食谱。不知道我理解什么是“配料配方”。
    • 我也不知道。这是模棱两可的命名。你认为它是一种可以作为配料的特殊配方,我认为它是一种配料配方。无论如何-您的答案与stackoverflow.com/a/10325249/82062相同
    【解决方案6】:

    您可以使用adaptor 使食谱表现得像一种成分:

    class RecipeIngredient : IIngredient
    {
        private readonly Recipe _recipe;
    
        public RecipeIngredient(Recipe recipe) {
            _recipe = recipe;
        }
    
        public void DoIngredientStuff() {
            // Do stuff with the recipe.
        }
    }
    

    【讨论】:

    • 我认为只有当你无法控制自适应事物时才应该使用适配器
    • 我认为在这种情况下使用它是一个合适的模式。它避免了食谱必须知道它们是否可以是成分,但允许任何食谱在需要时成为成分。你会如何解决这个问题?
    猜你喜欢
    • 2017-08-10
    • 2017-11-27
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2013-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多