从基类构造函数创建子类实例

4

我正在学习Java的面向对象编程。假设我有一个名为Car的基类,并有一些继承自Car的子类,比如Engine、FuelTank和Wheels。是否可以在Car的基类中实例化这些扩展类,以便每个Car实例都有自己的子类实例?

class Car{
      Car(){
          // instance of Engine
          // instance of Wheels
          // instance of FuelTank
      }
 }

class CarPart{}

class Engine extends CarPart{}

class Wheels extends CarPart{}

class FuelTank extends CarPart{}

5
这些类继承汽车没有任何意义。 - Eran
3
任何继承了Car的类本身都应该是(在某种意义上)一个Car。 我认为Engine,Wheels或Fuel Tank不是一种类型的Car。请看Oracle自己对此的解释:https://docs.oracle.com/javase/tutorial/java/concepts/inheritance.html - Chrs
2
编写类时要保持所建模型的连贯性,问自己一个问题:引擎是一种汽车吗? 如果是,那么做 Engine extends Car 就有意义。 - ΦXocę 웃 Пepeúpa ツ
2
好的,我添加了一个CarPart类,并使这些部件扩展了CarPart。Car类仍然可以创建每个CarPart子类的实例吗? - Kisner
2
当然可以…… - Jason V
显示剩余6条评论
3个回答

4

你的问题是:

在一个类中,我可以创建其他类的实例吗?

理论上和技术上,你可以很容易地得到答案“是”。但真正的答案是...“不可以”。因为,如果你这样做:

  • 你会将创建实例的类(Car)与被创建实例的类(EngineWheelsFuelTank)紧密耦合。这样,一辆车总是必须有一个油箱,即使可能会实现纯电力解决方案。
  • 汽车不仅要负责行驶在路上,还要自己创建汽车零件。这有点像“变形金刚”。

但是,如果你想正确地学习和应用面向对象编程,那么这两种情况都不是“允许”的选项。这就好比在普通游泳比赛中,你“可以”使用一对鱼鳍,但你会被取消资格。

说到这里,紧耦合唯一可行的替代方案就是松耦合:汽车零件被注入、传递、插入到汽车中。在面向对象编程中,这个过程称为依赖注入(DI)。汽车零件-依赖项-可以很容易地改变或删除。因此,燃油箱可以完全移除,因为汽车将只用电力驱动。汽车本身现在只有一个责任-上路行驶。即使它仍然依赖于它的零件-正如它应该的那样,汽车不再关心汽车零件的创建过程:它只接收它们-正如它应该的那样。
我突然意识到,我会使用另一个概念方案:引擎不应该是汽车零件。只有它插入到汽车中才能成为汽车零件。轮胎和燃油箱也是如此。简而言之,我会用其他方式来定义它们:
class Engine {...}
class Wheels {...}
class FuelTank {...}

class CarEngine extends Engine {...}
class CarWheels extends Wheels {...}
class CarFuelTank extends FuelTank {...}

所以,松耦合将应用如下:
class Car{

    Car(instance of CarEngine, instance of CarWheels, instance of CarFuelTank){
        //...
    }

}

或者更好的是,类比于您的代码版本:
class Car {

    Car(instance of Engine, instance of Wheels, instance of FuelTank){
        //...
    }

}

以下是两个非常好的资源,可以更好地理解这里介绍的原则:

祝你的项目顺利!


编辑1:

@Ryan The Leach 友好地并且正确地向我提出了要向您介绍工厂概念的建议。我会尝试以一种原则性的方式来解释。

汽车需要发动机才能运转。但是谁能生产这个汽车部件呢?嗯:一个发动机工厂。更准确地说是汽车发动机工厂。

在面向对象编程中,工厂是一个专门的类,具有单一职责:创建其他类的实例(听起来很熟悉吧?)。然后,根据它们的有用性,在其他地方使用所创建的实例。现在,当工厂创建一个对象或多个不同类类型的对象时,它可能还需要收集/生产其他资源,以便准备创建最终的“产品”。

因此,发动机工厂首先需要收集/生产大量的发动机零件,然后再组装/创建最终的发动机。汽车工厂也是如此:它需要从发动机工厂获得发动机,从轮胎工厂获得轮胎,从燃油箱工厂获得燃油箱。然后它生产最终的汽车。

在面向对象编程中,有三种类型的工厂,即所谓的工厂模式

  • 简单工厂
  • 工厂方法
  • 抽象工厂

我不会亲自介绍它们,但我会尝试引导您到一些非常好的Web资源,易于理解和跟随:

P.S:

前两个是我的最爱......尽管我还没有看过 :-) 是的,很傻,不是吗?因为它们是相对较新的上传,我不知道它们的存在。但是:我关注了作者的所有其他流,并用三个词来形容:我印象深刻。顺便说一下:作者解释了书籍"Head First Design Patterns"中呈现的所有模式。


2
你可能也应该展示OP工厂方法。 - Ryan Leach
有趣。谢谢您的反馈! - Kisner
@Kisner 不用谢。我刚刚添加了更多关于工厂的内容(这是一种基本的面向对象编程模式,你将在整个过程中一直使用它)。祝你好运。 - user7941334
@Kisner 我忘了向您介绍一些内容:汽车必须具有单一职责:行驶在道路上。这个“单一职责”实际上是面向对象编程中所谓的 S.O.L.I.D. 原则 中非常重要的一部分。请搜索著名的创始人 Robert C. Martin(或称 Uncle Bob)。这里是其中一个链接:Robert C Martin - 单一职责原则。再见,感谢您的赞赏。 - user7941334

2

从语法角度来看,是的,您可以在父类基类中声明并创建子类实例,如下所示。

public class Base {
     public void someMethod(){
         Child child1 = new Child(); // valid
         Base child2 = new Child(); // valid
    }
}

public class Child extends Base{

}

从良好的实践和面向对象设计的角度来看:您不应该在基类中创建子类的对象。
只有当B行为像A并具有一些附加行为时,您才应该从A继承类B。通常情况下,基类是在子类之前编写的。在编写基类时,您无法知道将来会有哪些类以及将有多少个类扩展它。您应该遵循不修改现有类的实践(称为开闭原则)。当然,要求会得到精炼、重新定义,并导致重构类或设计。这是一个原则,一条指南,您不必过于严格地遵循它,但始终记住它,每当您违反它时,您必须有一个非常好的理由。
继承本身不是一个非常好的实践,因为它将父类的内部暴露给子类。它通常被称为白盒复用。它以某种方式破坏了封装性。为此,您应该优先考虑组合而不是继承。
在您的情况下,您的Car类应该具有对象Wheel、Engine等的实例引用字段。
如果您想使代码更加灵活,可以进一步定义Wheel、Engine接口,然后让您的类Car仅依赖于这些接口。这将保护您,以防明天您决定添加新类型的Wheel(如NormalWheel、AlloyWheel),或者您决定创建具有增强不同引擎实现的汽车实例,那么这些类只需符合定义的接口,所需的Engine或Wheel的具体对象就可以被注入到Car类中。依赖于接口而不是具体类可保护您免受稍后修改已经测试和稳定的代码的影响。

0

让我们考虑您的汽车示例。

基类应该是您选择为类的一般形式。它不应该是具体的。

扩展类或子类应该是基类的特定版本。如果汽车是您的基类,那么您的子类应该是跑车出租车赛车等。

例如,跑车汽车的一个特定版本,具有更多特定于其任务的功能。

子类和基类之间应该有一个是一个关系

“子类”是“基类”

希望这对您有所帮助......


引擎、车轮和油箱是汽车零件,这是否太泛泛了?也许这些类应该成为它们自己的基类? - Kisner
你可以说引擎、车轮和油箱是汽车零件的子类,这完全没有问题。当你开发程序时,如果有一些具有共同特征或行为的类,可以使用它们来创建一个基类。 - LSampath
并不总是有关系的就意味着继承。考虑“正方形是矩形”的例子。尝试研究一下为什么在这里从矩形继承正方形是一个坏主意。【提示:图形应用程序具有矩形,而矩形在具有定义宽度和高度的画布区域中使用。如果正方形被多态地用于代替矩形作为矩形的子类,现在用户尝试更改高度,会发生什么..?】 - nits.kk
好的。有一些特殊情况。我们的 Rectangle 可能有两种方法来独立改变高度和宽度。但是 Square 类不应该独立地改变它们。但是通过一些简单的编码仍然是可能的。 - LSampath
通过这个简单的编码,你需要遵循以下原则:空方法体 - 不良实践,只抛出异常的方法 - 不良实践。子类应该是其基类的完美替代品。每个方法调用必须遵循基类中方法声明所承诺的内容(S.O.L.I.D中的LSP)。 - nits.kk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接