C# 如何将某些行为限制为特定类的实例子集?

C# 如何将某些行为限制为特定类的实例子集?,c#,domain-driven-design,data-modeling,C#,Domain Driven Design,Data Modeling,我正在为一个配方应用程序开发域模型,遇到了一个问题 该应用程序具有多个能够充当成分的实体,其中两个实体是:产品和配方(配方可以是其他配方中的成分)。通常,我会将与成分相关的功能封装到每个实体都可以实现的接口中。问题是,尽管所有产品实例都可以是配料,只有配方实例的子集可以是配料 interface IIngredient { void DoIngredientStuff(); } class Product : IIngredient { void DoIngredientStu

我正在为一个配方应用程序开发域模型,遇到了一个问题

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

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
    }
}
接口组件
{
void DoIngredientStuff();
}
类别产品:iIngElement
{
void DoIngredientStuff()
{
//所有产品都是配料——随意做配料
}
}
课程配方:iIngElement
{
公共IEnumerable成分{get;set;}
void DoIngredientStuff()
{
//并非所有的食谱都是配料——这可能是非法的
}
}
如何重新构造此模型以支持只有某些配方实例才能作为配料的要求?

interface iingredent
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
      }
    } 
} 
{ 布尔ISingElement{get;} void DoIngredientStuff(); } 课程配方:iIngElement { 公共IEnumerable成分{get;set;} 布尔情报部门{ get{return true;//如果是true,则为true;如果不是false} } void DoIngredientStuff() { 如果(ISingCredit){ //做任何事 } } }
听起来像是设计问题。如果您必须开始为iSingCredit进行测试,我认为您的设计出了问题。当你有另一种特殊情况时会发生什么?然后是另一个?你会继续添加特殊的If测试还是一个大的switch语句?这打破了开闭原则

与其继承,不如创作?你可能还想看看战略模式


实际上,这里的核心问题是Recipe不应该实现iIngRelient…因为并非所有Recipe都实现iIngRelient行为…

如果在类树中为这两种类型的Recipe设置单独的类,那么另一个选项是。请注意,如果要通过更多属性来区分对象,则此方法不起作用

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
    }
}
类配方{
公共IEnumerable成分{get;set;}
}
类有用配方:配方,成分
{
void DoIngredientStuff()
{
//并非所有的食谱都是配料——这可能是非法的
}
}
您可以使用一个按钮使配方表现为一种配料:

class RecipeIngredient : IIngredient
{
    private readonly Recipe _recipe;

    public RecipeIngredient(Recipe recipe) {
        _recipe = recipe;
    }

    public void DoIngredientStuff() {
        // Do stuff with the recipe.
    }
}

我可能会使用composition创建一个可以组合2个(可能更多)配方的
CompositeRecipe
。没有将所有配方都用作新配方基础的原因是什么?你可以添加一个布尔属性来表示配方是一个完整的配方(
IsCompleteRecipe
),然后取决于你如何构建应用程序和应用程序逻辑来确定配方是否应该与其他配方组合。

如果只有一些配方是成分,那么这似乎是继承的经典案例,在子类上实现
iIngElement
时:

class Product : IIngredient
{
    void DoIngredientStuff();
}

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

class IngredientRecipe : Recipe, IIngredient
{
    void DoIngredientStuff();
}
类别产品:iIngElement
{
void DoIngredientStuff();
}
课堂食谱
{
公共IEnumerable成分{get;set;}
}
分类中心:配方、成分
{
void DoIngredientStuff();
}

这会起作用……但这是一个有缺陷且不可扩展的设计。每次需要在设计中添加新的特殊条件时,它都需要重构。我不认为它有缺陷。这是.NET System.IO.Stream库使用的方法。有些流支持读取,有些流不支持读取,这是通过布尔属性传递的。在你的例子中,有些食谱是成分,有些不是,这是表达这一点的最简单方式之一。此外,如果要持久化这些实体,则更容易避免多态方法,而不是选择在每个配方中存储一个位字段,以指示其是否可以用作配料。这是有缺陷的。违反了最小惊讶原则()。如果它是一种成分,它必须能够做一些有用的东西。否则-强制执行它的接口有什么意义?@eulerfx.NET framework充满了难看的代码。我不认为引用流库作为示例是错误的。然而,如果以这种方式来看,你不认为你的两个参考文献都是诉诸权威谬论的例子吗?SmartRecipe,UsefulRecipe,RecipeWithonLiveInstructions,RecipeThatCanlyLiveYearSelled,MyGrandmash,AlfandreAroldRecipe ExtrastyPancakes that WeatNewYears我试图在我的答案中写道,但它在第二线,而且。。。如果你有更好的表达方式,请随意编辑答案。我认为只有当你无法控制适应性的东西时,才应该使用适配器。我认为在这种情况下使用适配器是合适的模式。它避免了食谱必须知道它们是否可以成为配料,但允许任何食谱在需要时成为配料。你会如何处理这个问题?撰写食谱并不能解决并非所有食谱都是无效的问题。这不是关于成分的食谱,而是关于一些可以作为成分的食谱。有一个巨大的区别。在这个设计中,IngredCenter就是这样:一个可以作为配料的配方。我不知道什么是“配料配方”,我也不知道。这是个模棱两可的名字。你把它看作是一个可以作为配料的特殊配方,我把它看作是配料的配方。无论如何,你们的答案和找出好答案是一样的,我们需要理解为什么所有的食谱都不能作为配料