如何实现RealNumber和ComplexNumber继承?


11

希望不是太学术...

假设我的SW库中需要实数和复数。

根据is-a(或here)关系,实数复数,其中复数虚部中的b就是0。

另一方面,我的实现是,该子对象扩展父对象,因此在父对象RealNumber中,我将拥有实部,而子对象ComplexNumber将添加虚构图。

还有一种观点认为继承是邪恶的

我记得像昨天一样,当我在大学学习OOP时,我的教授说,这不是继承的一个很好的例子,因为这两者的绝对值是用不同的方式计算的(但为此我们有方法重载/多态主义,对吗?)。 。

我的经验是,我们经常使用继承来解决DRY,因此,我们经常在层次结构中人工构造抽象类(由于名称不能代表真实世界中的对象,因此经常很难找到名称)。


7
这看起来像先前的问题所涵盖:矩形是否应该从正方形继承?
蚊蚋

1
@gnat哦,伙计,这是我要使用的另一个示例...谢谢!
Betlista

7
...请注意,从数学意义上讲,“实数是复数”一词仅对不可变数有效,因此,如果使用不可变对象,则可以避免LSP违反(对于正方形和矩形也是如此,请参见所以回答)。
布朗

5
...请注意,复数的绝对值计算也适用于实数,因此我不确定您的教授是什么意思。如果您以不变的复数正确地实现“ Abs()”方法并从中得出“实数”,则Abs()方法仍将提供正确的结果。
布朗

Answers:


17

即使从数学意义上讲,实数是复数,从复数导出实数也不是一个好主意。它违反了Liskov替代原则(其中包括),派生类不应隐藏基类的属性。

在这种情况下,实数将不得不隐藏复数的虚部。显然,如果只需要实数部分,则存储隐藏的浮点数(虚数部分)是没有意义的。

这基本上与注释中提到的矩形/正方形示例相同。


2
今天,我多次看到这个“ Liskow替代原则”,我将不得不阅读更多有关它的内容,因为我不知道这一点。
Betlista

7
将实数的虚部报告为零(例如通过只读方法)完全可以。但是,将虚部设置为零将实数实现为复数是没有意义的。这正是继承引起误导的一种情况:尽管接口继承在这里可以说是很好的,但实现继承会导致有问题的设计。
阿蒙(Amon)

4
只要实数从复数继承,这是完全有意义的,只要两者都是不可变的。而且您不介意开销。
Deduplicator

@Deduplicator:有趣的一点。不变性解决了许多问题,但在这种情况下,我还没有完全确信。必须考虑一下。
Frank Puffer

3

继承不是一个很好的例子,因为这两个的绝对值计算方式不同

实际上,这并不是反对所有继承的令人信服的原因,只是建议的class RealNumber<-> class ComplexNumber模型。

您可能会合理定义一个接口Number,该接口和接口 将实现。RealNumberComplexNumber

可能看起来像

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

但是随后您希望将Number这些操作中的其他参数限制为与相同的派生类型this,您可以通过

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

或者,您将使用允许结构多态性而不是子类型多态性的语言。对于数字的特定情况,您可能只需要能够重载算术运算符。

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations

0

解决方案:没有公开RealNumber

如果ComplexNumber使用静态工厂方法fromDouble(double)返回虚数为零的复数,我将完全可以。然后,您可以在该RealNumber实例上使用您将在该实例上使用的所有操作ComplexNumber

但是我很难理解为什么您想要/需要一个公共的继承RealNumber类。通常,出于以下原因使用继承(在我的脑海中,如果错过了一些,请纠正我)

  • 扩展行为。RealNumbers无法执行复数不能执行的任何额外操作,因此这样做毫无意义。

  • 通过特定的实现来实现抽象行为。由于ComplexNumber不应该抽象,所以这也不适用。

  • 代码重用。如果只使用ComplexNumber该类,则将重用100%的代码。

  • 针对特定任务的更加具体/高效/准确的实现。这可以在这里应用,RealNumbers可以更快地实现某些功能。但是,此子类应该隐藏在静态对象的后面,fromDouble(double)并且不应在外部被知道。这样,就不必隐藏虚部。外面应该只有复数(实数是)。您还可以从产生实数的复数类中的任何操作返回此私有RealNumber类。(这假定类与大多数数字类一样是不变的。)

这就像实现一个称为零的Integer的子类,并对某些操作进行硬编码,因为它们对于零而言是微不足道的。您可以这样做,因为每个零都是整数,但是不要将其公开,而是将其隐藏在工厂方法后面。


我没有任何不足之处,因为我没有任何证据可以证明。另外,如果没有其他人有一个主意,我总是怀疑可能有某些原因。但是,请告诉我为什么您认为这是错误的,以及如何将其改进。
findusl

0

说实数是复数在数学(尤其是集合论)中比计算机科学具有更多的意义。
在数学中,我们说:

  • 实数是复数,因为复数集包括实数集。
  • 有理数是实数,因为实数集包括有理数集(和无理数集)。
  • 整数是有理数,因为有理数集包括整数集。

但是,这并不意味着您在设计包含RealNumber和ComplexNumber类的库时必须甚至应该使用继承。在有效的Java,第二版由约书亚布洛赫; 第16项是“继承时的有利组成”。为避免该项目中提到的问题,一旦定义了RealNumber类,便可以在ComplexNumber类中使用它:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

这样,您就可以重用RealNumber类,使代码保持DRY,同时避免Joshua Bloch确定的问题。


0

这里有两个问题。首先,通常对容器的类型及其内容的类型使用相同的术语,尤其是对于诸如数字之类的原始类型。double例如,该术语用于描述双精度浮点值和可以存储一个值的容器。

第二个问题是,虽然is-a可以读取各种类型的对象的容器之间的关系与对象本身之间的关系相同,但是可以放置各种类型的对象的容器之间的关系与它们的内容之间的关系相反。每个已知拥有实例的Cat笼子都是拥有实例的笼子Animal,但不一定是拥有实例的笼子SiameseCat。另一方面,每个可以容纳的所有实例的笼子都是可以容纳的所有实例的Cat笼子SiameseCat,但不必是可以容纳的所有实例的笼子Animal。唯一一种可以容纳的所有实例Cat并且可以保证绝不容纳任何实例的笼子Cat,是的笼子Cat。任何其他种类的笼子要么不能接受Cat它应该接受的某些实例,要么能够接受不是实例的事​​物Cat

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.