41

我在工作中讨论了“领域模型中的继承使开发人员的生活变得复杂”。我是一名 OO 程序员,所以我开始寻找在域模型中继承将简化开发人员生活而不是到处进行切换的论点。

我想看到的是:

class Animal {

}

class Cat : Animal {

}

class Dog : Animal {

}

另一位同事说的是:

public enum AnimalType {
    Unknown,
    Cat,
    Dog
}

public class Animal {

    public AnimalType Type { get; set; }

}

我如何说服他(链接是WELCOME)类层次结构比在这种情况下拥有 enum 属性更好?

谢谢!

4

6 回答 6

27

这是我的推理方式:

仅当角色/类型永远不会改变时才使用继承。例如

将继承用于以下方面:

消防员 <- 员工 <- 人是错误的。

一旦消防员弗雷迪换工作或失业,您必须杀死他并重新创建一个新类型的新对象,并附有所有旧关系。

因此,对上述问题的天真解决方案是将 JobTitle 枚举属性赋予 person 类。这在某些情况下就足够了,例如,如果您不需要与角色/类型相关的非常复杂的行为。

更正确的方法是给 person 类一个角色列表。每个角色代表例如具有时间跨度的工作。

例如

freddy.Roles.Add(new Employement( employmentDate, jobTitle ));

或者如果这是矫枉过正:

freddy.CurrentEmployment = new Employement( employmentDate, jobTitle );

这样,弗雷迪就可以成为一名开发人员,而我们不必先杀了他。

但是,如果您应该使用枚举或类型层次结构作为职称,我的所有杂谈仍然没有得到解答。

在纯内存 OO 中,我会说在这里使用继承作为职称更正确。

但是,如果您正在执行 O/R 映射,如果映射器尝试将每个子类型映射到新表,那么您最终可能会在幕后使用过于复杂的数据模型。因此,在这种情况下,如果没有与类型相关的真实/复杂行为,我通常会采用枚举方法。如果使用有限并且它使事情变得更容易或更不复杂,我可以接受“if type == JobTitles.Fireman ...”。

例如,.NET 的 Entity Framework 4 设计器只能将每个子类型映射到一个新表。当您查询数据库而没有任何实际好处时,您可能会得到一个丑陋的模型或大量连接。

但是,如果类型/角色是静态的,我会使用继承。例如产品。

您可能有 CD <- 产品和书籍 <- 产品。继承在这里获胜,因为在这种情况下,您很可能具有与类型关联的不同状态。CD 可能具有许多曲目属性,而一本书可能具有页数属性。

所以简而言之,这取决于;-)

此外,在一天结束时,您很可能最终会得到很多 switch 语句。假设你想编辑一个 "Product" ,即使你使用继承,你可能会有这样的代码:

if (product is Book) Response.Redicted("~/EditBook.aspx?id" + product.id);

因为在实体类中对编辑书 url 进行编码会很丑陋,因为它会迫使您的业务实体了解您的网站结构等。

于 2010-11-23T09:57:47.753 回答
10

枚举适用于以下情况:

  1. 这组值是固定的,从不或很少改变。
  2. 您希望能够表示值的联合(即组合标志)。
  3. 您不需要将其他状态附加到每个值。(Java 没有这个限制。)

如果你可以用一个数字来解决你的问题,那么枚举可能是一个很好的选择并且更安全。如果您需要比上述更多的灵活性,那么枚举可能不是正确的答案。使用多态类,您可以:

  1. 静态确保处理所有类型特定的行为。例如,如果您需要所有动物都能够做到Bark()Animal那么使用抽象Bark()方法创建类将使编译器为您检查每个子类是否实现了它。如果您使用 enum 和 big switch,则无法确保您处理了所有情况。

  2. 您可以添加新案例(示例中的动物类型)。这可以跨源文件完成,甚至可以跨包边界完成。使用枚举,一旦你声明它,它就会被冻结。开放式扩展是 OOP 的主要优势之一。

请务必注意,您同事的榜样与您的榜样并不直接相反。如果他希望动物的类型成为公开属性(这对某些事情很有用),您仍然可以在不使用枚举的情况下使用类型对象模式来做到这一点:

public abstract class AnimalType {
    public static AnimalType Unknown { get; private set; }
    public static AnimalType Cat { get; private set; }
    public static AnimalType Dog { get; private set; }

    static AnimalType() {
        Unknown = new AnimalType("Unknown");
        Cat = new AnimalType("Cat");
        Dog = new AnimalType("Dog");
    }
}

public class Animal {
    public AnimalType Type { get; set; }
}

这为您提供了枚举的便利:您可以做到AnimalType.Cat并且可以获得动物的类型。但它也为您提供了类的灵活性:您可以添加字段以AnimalType存储每种类型的附加数据,添加虚拟方法等。更重要的是,您可以通过创建新的实例来定义新的动物类型AnimalType

于 2010-11-23T17:17:32.777 回答
10

Open/Closed Principle is for suckers拥有一个枚举就像为所有这些人举办一个派对。

它确实邀请您检查动物是否属于某种类型,然后为每种类型应用自定义逻辑。这可能会呈现可怕的代码,这使得在您的系统上继续构建变得非常困难。

于 2011-05-09T07:59:16.130 回答
8

我敦促您重新考虑:在贫血的域模型中(根据上面的评论),猫的行为与狗没有什么不同,因此不存在多态性。动物的类型实际上只是一个属性。很难看出那里的遗产能给你带来什么。

于 2010-11-23T17:38:35.280 回答
1

最重要的是,OOPS 意味着模拟现实。继承让你有机会说猫是动物。动物不应该知道它是不是一只猫现在喊它然后决定它应该是喵而不是树皮,封装在那里被打败了。更少的代码,你不必像你说的那样做 If else。

于 2010-11-23T09:56:19.937 回答
1

两种解决方案都是正确的。你应该看看哪些技术更适用于你的问题。

如果您的程序使用很少的不同对象,并且不添加新类,则最好使用枚举。

但是如果你的程序使用了很多不同的对象(不同的类),并且可能会添加新的类,那么将来最好尝试继承的方式。

于 2011-05-03T16:18:55.920 回答