我在工作中进行了一次关于“域模型中的继承使开发人员的生活变得复杂”的讨论。我是一名 OO 程序员,所以我开始寻找论据,证明在域模型中具有继承实际上可以减轻开发人员的生活,而不是到处进行切换。
我想看到的是:
class Animal {
}
class Cat : Animal {
}
class Dog : Animal {
}
另一位同事所说的是:
public enum AnimalType {
Unknown,
Cat,
Dog
}
public class Animal {
public AnimalType Type { get; set; }
}
我如何说服他(链接是 WELCOME )类层次结构会比拥有此类枚举属性更好情况?
谢谢!
I had a discussion at work regarding "Inheritance in domain model is complicating developers life". I'm an OO programmer so I started to look for arguments that having inheritance in domain model will ease the developer life actually instead of having switches all over the place.
What I would like to see is this :
class Animal {
}
class Cat : Animal {
}
class Dog : Animal {
}
What the other colleague is saying is :
public enum AnimalType {
Unknown,
Cat,
Dog
}
public class Animal {
public AnimalType Type { get; set; }
}
How do I convince him (links are WELCOME ) that a class hierarchy would be better than having a enum property for this kind of situations?
Thanks!
发布评论
评论(6)
拥有一个枚举就像为所有那些
开放/封闭原则是傻瓜
的人举办派对。它邀请您检查动物是否属于某种类型,然后为每种类型应用自定义逻辑。这可能会产生可怕的代码,这使得在您的系统上继续构建变得困难。
为什么?
“如果是这种类型,则执行此操作,否则执行此操作”会妨碍编写良好的代码。
每当引入新类型时,如果不处理新类型,所有这些 if 都会无效。在较大的系统中,很难找到所有这些“如果”,这最终会导致错误。
更好的方法是使用小型的、定义良好的功能接口(接口隔离原则)。
那么你将只有一个
if
而没有“else”,因为所有具体的东西都可以实现特定的功能。比较
看
?前者需要检查一次,而后者则必须对每只会飞的动物进行检查。
Having an enum is like throwing a party for all those
Open/Closed Principle is for suckers
people.It invites you to check if an animal is of a certain type and then apply custom logic for each type. And that can render horrible code, which makes it hard to continue building on your system.
Why?
Doing "if this type, do this, else do that" prevents good code.
Any time you introduce a new type, all those ifs get invalid if the new type is not handled. In larger systems, it's hard to find all those ifs, which will lead to bugs eventually.
A much better approach is to use small, well-defined feature interfaces (Interface segregation principle).
Then you will only have an
if
but no 'else' since all concretes can implement a specific feature.Compare
to
See? the former one needs to be checked once while the latter has to be checked for every animal that can fly.
枚举适用于以下情况:
如果您可以用数字解决您的问题,那么枚举可能是一个不错的选择,并且类型更安全。如果您需要比上述更多的灵活性,那么枚举可能不是正确的答案。使用多态类,您可以:
静态地确保所有特定于类型的行为都得到处理。例如,如果您需要所有动物都能够
Bark()
,那么使用抽象Bark()
方法创建Animal
类将让编译器会检查每个子类是否实现了它。如果您使用枚举和大型开关
,则无法确保您已处理所有情况。您可以添加新案例(示例中的动物类型)。这可以跨源文件甚至跨包边界来完成。对于枚举,一旦声明它,它就会被冻结。开放式扩展是 OOP 的主要优势之一。
值得注意的是,你同事的例子并不与你的例子直接相反。如果他希望动物的类型成为公开属性(这对某些事情很有用),您仍然可以在不使用枚举的情况下使用 类型对象模式:
这为您提供了枚举的便利:您可以执行
AnimalType.Cat
并且可以获得动物的类型。但它也为您提供了类的灵活性:您可以向AnimalType
添加字段来存储每种类型的附加数据、添加虚拟方法等。更重要的是,您可以通过创建新的动物类型来定义新的动物类型AnimalType
的实例。Enums are good when:
If you could solve your problem with a number, an enum is likely a good fit and more type safe. If you need any more flexibility than the above, then enums are likely not the right answer. Using polymorphic classes, you can:
Statically ensure that all type-specific behavior is handled. For example, if you need all animals to be able to
Bark()
, makingAnimal
classes with an abstractBark()
method will let the compiler check for you that each subclass implements it. If you use an enum and a bigswitch
, it won't ensure that you've handled every case.You can add new cases (types of animals in your example). This can be done across source files, and even across package boundaries. With an enum, once you've declared it, it's frozen. Open-ended extension is one of the primary strengths of OOP.
It's important to note that your colleague's example is not in direct opposition to yours. If he wants an animal's type to be an exposed property (which is useful for some things), you can still do that without using an enum, using the type object pattern:
This gives you the convenience of an enum: you can do
AnimalType.Cat
and you can get the type of an animal. But it also gives you the flexibility of classes: you can add fields toAnimalType
to store additional data with each type, add virtual methods, etc. More importantly, you can define new animal types by just creating new instances ofAnimalType
.我敦促您重新考虑:在贫血域模型中(根据上面的评论),猫行为与狗没有什么不同,因此不存在多态性。动物的类型实际上只是一个属性。很难看出继承权能在那里给你带来什么。
I'd urge you to reconsider: in an anemic domain model (per the comments above), cats don't behave differently than dogs, so there's no polymorphism. An animal's type really is just an attribute. It's hard to see what inheritance buys you there.
最重要的是,OOPS 意味着对现实进行建模。继承使您有机会说 Cat 是一种动物。动物不应该知道它是否是一只猫,现在对它大喊大叫,然后决定它应该喵叫而不是吠叫,封装在那里被击败。更少的代码,因为现在您不必像您所说的那样执行 If else 。
Most importantly OOPS means modeling reality. Inheritance gives you the opportunity to say Cat is an animal. Animal should not know if its a cat now shout it and then decide that it is suppose to Meow and not Bark, Encapsulation gets defeated there. Less code as now you do not have to do If else as you said.
两种解决方案都是正确的。
您应该看看哪种技术更适合您的问题。
如果您的程序使用很少的不同对象,并且不添加新类,那么最好保留枚举。
但如果你的程序使用了很多不同的对象(不同的类),并且可能会添加新的类,那么以后最好尝试继承的方式。
Both solutions are right.
You should look which techniques applies better to you problem.
If your program uses few different objects, and doesn't add new classes, its better to stay with enumerations.
But if you program uses a lot of different objects (different classes), and may add new classes, in the future, better try the inheritance way.
我的推理如下:
仅当角色/类型永远不会改变时才使用继承。
例如,
对以下内容使用继承:
Fireman <- Employee <- Person 是错误的。
一旦消防员弗雷迪换工作或失业,你就必须杀死他并重新创建一个新类型的新对象,并附加所有旧的关系。
因此,解决上述问题的简单方法是为 person 类提供 JobTitle 枚举属性。
在某些情况下这已经足够了,例如,如果您不需要与角色/类型关联的非常复杂的行为。
更正确的方法是为人员类别提供角色列表。
例如,每个角色代表一个具有时间跨度的工作。
例如
,或者如果这太过分了:
这样,弗雷迪就可以成为一名开发人员,而我们不必先杀死他。
然而,我所有的漫谈仍然没有回答你是否应该使用枚举或类型层次结构作为职位。
在纯粹的内存面向对象中,我想说在这里对职位使用继承是更正确的。
但是,如果您正在进行 O/R 映射,如果映射器尝试将每个子类型映射到新表,您最终可能会在幕后得到有点过于复杂的数据模型。
因此,在这种情况下,如果没有与类型相关的真实/复杂行为,我通常会采用枚举方法。
如果使用受到限制并且它使事情变得更容易或更简单,我可以接受“if type == JobTitles.Fireman ...”。
例如,.NET 的 Entity Framework 4 设计器只能将每个子类型映射到一个新表。当您查询数据库时,您可能会得到一个丑陋的模型或大量的连接,而没有任何真正的好处。
但是,如果类型/角色是静态的,我确实会使用继承。
例如,对于产品。
您可能有 CD <- 产品和书籍 <- 产品。
继承在这里获胜,因为在这种情况下,您很可能具有与类型关联的不同状态。
CD 可能具有多个曲目属性,而书籍可能具有多个页数属性。
简而言之,这取决于 ;-)
另外,最终您很可能会得到很多 switch 语句。
假设你想编辑一个“Product”,即使你使用继承,你也可能有这样的代码:
if (product is Book)
Response.Redicted("~/EditBook.aspx?id" + 产品.id);
因为在实体类中编码编辑书 url 会很丑陋,因为它会迫使您的业务实体了解您的网站结构等。
Here is how I reason about it:
Only use inheritance if the role/type will never change.
e.g.
using inheritance for things like:
Fireman <- Employee <- Person is wrong.
as soon as Freddy the fireman changes job or becomes unemployed, you have to kill him and recreate a new object of the new type with all of the old relations attached to it.
So the naive solution to the above problem would be to give a JobTitle enum property to the person class.
This can be enough in some scenarios, e.g. if you don't need very complex behaviors associated with the role/type.
The more correct way would be to give the person class a list of roles.
Each role represents e.g an employment with a time span.
e.g.
or if that is overkill:
This way , Freddy can become a developer w/o we having to kill him first.
However, all my ramblings still haven't answered if you should use an enum or type hierarchy for the jobtitle.
In pure in mem OO I'd say that it's more correct to use inheritance for the jobtitles here.
But if you are doing O/R mapping you might end up with a bit overcomplex data model behind the scenes if the mapper tries to map each sub type to a new table.
So in such cases, I often go for the enum approach if there is no real/complex behavior associated with the types.
I can live with a "if type == JobTitles.Fireman ..." if the usage is limited and it makes things easer or less complex.
e.g. the Entity Framework 4 designer for .NET can only map each sub type to a new table. and you might get an ugly model or alot of joins when you query your database w/o any real benefit.
However I do use inheritance if the type/role is static.
e.g. for Products.
you might have CD <- Product and Book <- Product.
Inheritance wins here because in this case you most likely have different state associated with the types.
CD might have a number of tracks property while a book might have number of pages property.
So in short, it depends ;-)
Also, at the end of the day you will most likely end up with a lot of switch statements either way.
Let's say you want to edit a "Product" , even if you use inheritance, you will probably have code like this:
if (product is Book)
Response.Redicted("~/EditBook.aspx?id" + product.id);
Because encoding the edit book url in the entity class would be plain ugly since it would force your business entites to know about your site structure etc.