简体   繁体   English

继承:为什么更改对象的类型被认为是糟糕的设计?

[英]Inheritance: why is changing an object's type considered bad design?

While modelling my domain classes, I found that Entity Framework lets you model inheritance relationships, but doesn't support promoting a base type instance to its derived type, ie changing an existing Person row in the database to an Employee, which derives from Person. 在对域类进行建模时,我发现Entity Framework允许您对继承关系进行建模,但不支持将基类型实例提升为其派生类型,即将数据库中现有的Person行更改为派生自Person的Employee。
Apparently, I wasn't the first to wonder about this, because this question has been asked and answered on Stack Overflow multiple times (eg see here and here ). 显然,我并不是第一个对此感到疑惑的人,因为这个问题已经多次在Stack Overflow上被提出并回答(例如,见此处此处 )。

As indicated in these answers, Entity Framework doesn't support this because this isn't allowed in Object-Oriented Programming either: once an instance is created, you cannot change its run-time type. 如这些答案中所示,实体框架不支持这一点,因为在面向对象编程中不允许这样做:创建实例后,您无法更改其运行时类型。
Fair enough, from a technical perspective I can understand that once a single block of memory is allocated for an object, tacking on a few extra bytes afterwards to hold the derived type's fields in will probably require the entire memory block to be reallocated, and as a consequence, will mean that the object's pointer has now changed, which in turn introduces even more problems. 很公平,从技术角度来看,我可以理解,一旦为一个对象分配了一个单独的内存块,之后添加一些额外的字节来保存派生类型的字段可能需要重新分配整个内存块,并且结果,意味着对象的指针现在已经改变,这反过来又引入了更多的问题。 So I get that this would be difficult to implement, and therefore not supported in C#. 所以我觉得这很难实现,因此在C#中不受支持。

However, aside from the technical component, the answers (and here too, see final paragraph on page 1) also seem to imply that it is considered bad design when you want to change an objects run-time type, saying that you shouldn't need this kind of type-changing and should use composition for these situations instead. 但是,除了技术组件之外,答案( 这里也是第1页的最后一段)似乎也暗示当你想要更改对象运行时类型时它被认为是糟糕的设计 ,说你不应该需要这种类型改变,而应该使用组合来代替这些情况。

Frankly, I don't get why - I'd say its perfectly valid to want to work Employee instances in the same way as you would with a Person instance (ie by inheriting Employee from Person), even if at some point in time a Person will be hired as an Employee, or an Employee quits and becomes a Person again. 坦率地说,我不明白为什么 - 我说完全有效的想要以与使用Person实例相同的方式工作Employee实例(即通过从Person继承Employee),即使在某个时间点a人员将被雇用为员工,或者员工退出并再次成为人员。
Conceptually, I don't see anything wrong with this? 从概念上讲,我没有看到任何错误?

Can anyone explain this to me? 任何人都可以向我解释这个吗?

--Edit, to clarify, why is this considered bad design: - 编辑,澄清,为什么这被认为是糟糕的设计:

public class Person
{
    public string Name { get; set; }
}

public class Employee: Person
{
    public int EmployeeNr { get; set; }

    public decimal Salary { get; set; }
}

...but this isn't? ......但这不是?

public class Person
{
    public string Name { get; set; }

    public EmployeeInfo EmployeeInfo { get; set; }
}

public class EmployeeInfo
{
    public int EmployeeNr { get; set; }

    public decimal Salary { get; set; }
}

Consider this code: 考虑以下代码:

Person person   = new Person { Name = "Foo" };
Person employee = new Employee { Name = "Bar", EmployeeNr = 42 };

However employee is declared as Person , the actual instance it is referring to is of type Employee . 但是, employee被声明为Person ,它所引用的实际实例是Employee类型。 This means you can cast it to that: 这意味着您可以将其强制转换为:

Employee employee2 = (Employee)employee;

When you try that with person : 当你和person一起尝试时:

Employee employee3 = (Employee)person;

You'll get an exception in runtime: 您将在运行时获得异常:

Unable to cast object of type 'Person' to type 'Employee'. 无法将“Person”类型的对象强制转换为“Employee”类型。

So you're right in that this is a technical impossibility, as C# is implemented. 所以你是对的,因为C#是实现的,这是技术上的不可能性。 You can work around it though, by creating a constructor in Employee : 您可以通过在Employee创建构造函数来解决它:

public Employee(Person person, int employeeNr, decimal salary)
{
    this.Name = person.Name;
    EmployeeNr = employeeNr;
    Salary = salary;
}

As you see, you'll have to instantiate the Person part of the Employee yourself. 如您所见,您必须自己实例化EmployeePerson部分。 This is tedious and error-prone code, but now we come to your actual question: why could this be considered a inferior design? 这是一个单调且容易出错的代码,但现在我们来看你的实际问题:为什么这可能被视为劣质设计?

The answer lies in favoring composition over inheritance : 答案在于有利于组合而不是继承

Composition over inheritance (or Composite Reuse Principle) in object-oriented programming is a technique by which classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance . 面向对象编程中的继承(或复合重用原则)组合是一种技术, 通过包含实现所需功能而不是通过继承的其他类,类可以实现多态行为和代码重用

The essence: 精华:

To favor composition over inheritance is a design principle that gives the design higher flexibility, giving business-domain classes and more stable business domain in the long term. 支持组合优于继承是一种设计原则,它赋予设计更高的灵活性,从长远来看,提供业务领域类和更稳定的业务领域。 In other words, HAS-A can be better than an IS-A relationship. 换句话说, HAS-A可能比IS-A关系更好。

To keep in the terms of your real-world example: in the future, we might have to employ Android s, who definitely don't inherit from Person , but could use a EmployeeInfo record. 为了保持你的真实世界的例子:将来,我们可能不得不使用Android ,他们肯定不会从Person继承,但可以使用EmployeeInfo记录。


As for your Entity Framework specific question: 至于您的Entity Framework特定问题:

I'd say its perfectly valid to want to work Employee instances in the same way as you would with a Person instance (ie by inheriting Employee from Person), even if at some point in time a Person will be hired as an Employee, or an Employee quits and becomes a Person again . 我想要以与使用Person实例相同的方式工作Employee实例是完全有效的(即通过从Person继承Employee), 即使在某个时间点Person将被雇用为Employee,或者员工退出并再次成为人员

If, for now, you know you're only going to employ humans (don't forget YAGNI ), I cannot imagine an example where this issue would come up. 如果,现在,你知道你只会雇用人类(不要忘记YAGNI ),我无法想象这个问题会出现的例子。 When you want to show all persons, you iterate that repository: 当您想要显示所有人时,您将迭代该存储库:

using (var context = new MyContext())
{
    foreach (var person in context.Persons)
    {
        PrintPerson(person);
    }
}

And when you want to list all employees, you do exactly the same, but with the Employee repository: 当您想要列出所有员工时,您完全相同,但使用Employee存储库:

using (var context = new MyContext())
{
    foreach (var Employee in context.Employees)
    {
        PrintEmployee(Employee);
    }
}

And if in some cases you want to know whether someone is an employee, you'd have to hit the database again: 如果在某些情况下您想知道某人是否是员工,您必须再次访问数据库:

public Empoyee FindEmployeeByName(string name)
{
    using (var context = new MyContext())
    {
        return context.Employees.FirstOrDefault(e => e.Name == name);
    }
}

Which you can then call using a Person instance: 然后您可以使用Person实例调用它:

Empoyee employee = FindEmployeeByName(person.Name);

You are over-analyzing this. 你是在过度分析这个。 Polymorphism is very well supported in .NET and there's never any way that you can unsafely upcast, a friendly InvalidCastException is always there to remind you that you got it wrong. 多态性在.NET中得到了很好的支持,并且从来没有任何方法可以不安全地进行上传,友好的InvalidCastException始终会提醒您错误。 It can even be safely done in C++ (dynamic_cast<>), not exactly a language well-known for a lack of ways to shoot your leg off. 它甚至可以用C ++(dynamic_cast <>)安全地完成,而不是一种众所周知的语言,因为缺乏射击方法。

The runtime support for it is the key. 对它的运行时支持是关键。 Dbase engines don't have any. Dbase引擎没有。

It was the playground for venture capital around the turn of the century. 它是世纪之交的风险投资的游乐场。 Already flowing freely thanks to the dot com boom. 由于点com热潮,已经自由流动。 Objected oriented databases where promised as jet-packs for everybody. 面向对象的数据库,承诺为每个人提供喷气式飞机包装。 But that busted too, none of them ever close to being dominant. 但那也被破坏了,他们都没有接近占主导地位。 Ever heard of Gemstone, db4o, Caché, odbpp? 有没有听说过Gemstone,db4o,Caché,odbpp? Competing with existing engines is a tall task, performance and reliability is everything in that market segment and it is very hard to catch up to 20 years of fine-tuning. 与现有的引擎竞争是一个艰巨的任务,性能和可靠性是在该细分市场的一切 ,这是很难赶上20年的微调。 So there was just no point in adding support for it in a data access layer. 因此,在数据访问层中添加对它的支持毫无意义。

Frankly, I don't get why - I'd say its perfectly valid to want to work Employee instances in the same way as you would with a Person instance (ie by inheriting Employee from Person), even if at some point in time a Person will be hired as an Employee, or an Employee quits and becomes a Person again. 坦率地说,我不明白为什么 - 我说完全有效的想要以与使用Person实例相同的方式工作Employee实例(即通过从Person继承Employee),即使在某个时间点a人员将被雇用为员工,或者员工退出并再次成为人员。 Conceptually, I don't see anything wrong with this? 从概念上讲,我没有看到任何错误?

I understand this as Person -> Employee -> Person ? 我理解Person -> Employee -> Person IE it is still a person but he/she/it gets attributed with "employment" and then "downgraded" to just a person? IE仍然是一个人,但他/她/它被归为“就业”,然后“降级”为一个人?

Inheritance is very useful when the object does not change type during runtime, for example when you create an equity -object you can be quite sure you do not want to turn it into an FRA , even though both are subclasses of securities. object在运行时没有更改类型时,继承非常有用,例如,当您创建一个equity object时,您可以确定您不希望将其转换为FRA ,即使它们都是证券的子类。 But whenever something might change during runtime, such as behaviour, try using composition instead. 但是,只要某些事情在运行时可能发生变化(例如行为),请尝试使用组合。 In my previous example, the security subclasses might inherit from a top-level asset class, but the methods used for putting a market value on an asset should be put there by a behavioral pattern, such as the strategy pattern, since you might want to use different methods during one runtime . 在我之前的示例中,安全子类可能从顶级资产类继承,但用于在资产上设置市场价值的方法应该由行为模式(例如策略模式)放在那里,因为您可能希望在一个运行时使用不同的方法。

For the case you are describing you should not use inheritance. 对于您描述的情况,您不应该使用继承。 Always favor composition over inheritance whenever possible. 尽可能始终支持组合而不是继承。 If something changes during runtime, it should definitely not be put there by inheritance! 如果在运行时期间发生了某些变化,那么绝对不应该通过继承来放置它!

You could compare it to lives, a human is born a human and dies a human, a cat is born a cat and dies a cat, an object of a certain type should be created as one type and garbage collected as the same type. 你可以将它与生命进行比较,一个人生来就是一个人而死一个人,一个猫生下一只猫而死一只猫,某种类型的对象应该被创造为一种类型而垃圾收集为同一类型。 Don't change what something is, change how it behaves or what it knows, through behavioral composition. 不要通过行为构成来改变某些东西,改变它的行为方式或它所知道的东西。

I would recommend you to have a look at Design Patterns: Elements of Reusable Object-Oriented Software which quite thoroughly covers some very useful design patterns. 我建议你看一下设计模式:可重用面向对象软件的元素,它完全涵盖了一些非常有用的设计模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么代码中的表格布局被认为是错误的原因是什么? - What's the reason why table layout in code is considered bad? 在构造函数中进行冗长的操作会被认为是不好的设计吗? - Is it considered bad design to do lengthy operations in a constructor? 为什么这个对象类型在我身上改变 - Why is this object type changing on me 空类型的设计不好吗? - Is an empty type bad design? 为什么是“使用系统”; 不被认为是不好的做法? - Why is "using System;" not considered bad practice? 为什么使用 new 关键字耦合依赖项被认为是不好的? - Why is coupling to dependencies with the new keyword considered bad? 在存储库接口中使用可选参数会被视为糟糕的设计吗? - Would using optional parameters in a repository interface be considered bad design? 不可为 null 的引用类型:为什么编译器认为我的 object 可以为 null? - Non-nullable reference type: why is my object considered nullable by the compiler? 为什么这种打开类型的案例被认为是令人困惑的? - Why is this switch on type case considered confusing? 为什么枚举中的位置标记(如第一个或最后一个)被视为不良做法? - Why are position markers, like first or last, in an Enumeration considered bad practice?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM