为什么选修基础课能力不好
本文关键字:能力 基础课 为什么 | 更新日期: 2023-09-27 18:09:11
我正在阅读Head First Design Patterns一书。
有一个关于Duck
基类的例子:
class Duck
{
public void Quack()
{
}
public void Fly()
{
}
}
如果我创建一个不能像那样飞行的子类
class CantFlyButQuackDuck : Duck
{
// I can use base Fly() here, but this kind of duck cannot fly!
}
所以,书中说这很糟糕,因为儿童班不需要飞行,而且它什么都不需要覆盖。
但如果我不从Child类调用Fly()
,那也没关系。那为什么书上说它不好呢?我将在我的应用程序中使用这个子类,如:
CantFlyButQuackDuck myobj= new CantFlyButQuackDuck();
myobj.Quack();
如果子类不共享父类的一个或多个特性,那么有什么理由让它从父类继承?
继承背后的思想是基类的属性和方法必须由其所有子类共享。除此之外,每个孩子可能都有自己的专门属性和方法,以及覆盖继承的方法。在您的情况下,Duck
可以Fly()
,从中继承的所有实体也必须如此。不在子级上调用Fly
并不意味着子级不"知道"如何使用Fly()
。
事实上,这可能是一个使用接口的好场景。这个接口将有一个方法Quack()
,您的类可以实现它。然后,虽然它与Duck
共享Quack
的能力,但它不能够Fly()
,因此它没有从中继承,同时仍然保留一些共享的能力。
为了正确看待这一点,假设您有一个Whistle
,它也可以是Quack
,但不能是Fly
。由于根据您的逻辑,不在CantFlyButQuackDuck
上调用Fly
似乎是合理的,因此在Whistle
上也应该同样合理,只是很明显,Whistle
绝不是Duck
的一种。这意味着你应该寻找的是共享某些属性/方法,而不是在你的世界模型中暗示"X是Y"的关系。
因为通常构建类的方式是从最少的功能到最多的功能,这可以防止意外的功能被继承。
你通常会这样说:
class Bird
{
public void MakeSound()
{
// insert sound related code here
}
}
然后你根据需要继承
class Duck : Bird
{
public void Fly()
{
// insert flight code here
}
}
class FlightlessDuck : Bird {}
这样做,你绝对可以肯定不会飞的鸭子不会飞,而鸭子是。这两只鸭子都会嘎嘎叫(发出声音(,但只有一只会飞。
另一种方法是从基类继承Fly
方法,因此即使您没有在子类中实现任何代码,您仍然可以调用它,这可能会破坏一切。
更理想的情况是,你可以添加一个包含所有飞行相关方法的界面,这样你就可以确保所有需要飞行的鸟类都遵守相同的标准。但这超出了你的问题范围。
这就像给孩子糖果并告诉孩子不要吃!但是,嘿,这个孩子仍然可以(一旦有机会,很可能会(!!
实用:
在行业标准中,您通常应该处理他人编写的代码,或者其他人应该处理您的代码。你不能认为什么事情永远不会做!
从根本上讲:
这听起来甚至很荒谬!你告诉孩子们,嘿,这是你的父母,这是父母允许你做的。现在,就像一些真诚的孩子一样,你选择不使用这些功能。。。但你仍然可以。鸭子即使不该飞也能飞!
代码:
CantFlyButQuackDuck myobj= new CantFlyButQuackDuck();
myobj.Quack();
//so okay if you dont call fly() method.Everything is ok!
// 4months later in the code. Bob comes here and observes ... heck! my duck can Fly ... why not!!
myobj.Fly(); // BOOM!
您有三个覆盖Fly
的选项。
- 不要覆盖,但这意味着基地苍蝇会被调用,它可能会在不该飞的时候飞
- 使用空方法重写。这至少可以阻止对基础的调用,但调用者不会从中得到任何反馈
- 覆盖fly并引发异常。这不会阻止开发人员调用它,但会阻止在运行时调用fly
这取决于你的情况。你以后想知道为什么某只鸭子在你叫它飞的时候没有飞吗?或者,当你试图让某只鸭子飞起来时,你希望程序失败吗?
在实践中,我们宁愿有一个替代解决方案:
目前,您的基类表示这两种方法是内聚的。如果子代实现了一个,则它必须同时实现这两个或保持基本行为。因此,为了解决这个问题,我们可以将您的接口分离为内聚单元:
interface IFly {
void Fly();
}
interface IQuack {
void Quack();
}
或者我们可以稍后在树中添加fly
方法来解决:
class QuackingDuck
{
public void Quack()
{
}
}
class FlyingDuck : QuackingDuck
{
public void Fly()
{
}
}
class CantFlyButQuackDuck : QuackingDuck
{
}
或者你可以把这两种技术结合起来。
无论您是否选择调用Fly()
,都可以(错误地(调用,并且将是所有CantFlyButQuackDuck
对象的成员,尽管该成员在不能(也不应该(对其执行任何操作的对象的上下文中没有位置。
类似的问题可以应用于许多OOP原则。这只是一个以可扩展和合理的方式组织和分解代码的例子。如果您以现有方式扩展Duck
,您的代码将编译并运行良好,但稍后会遇到问题。
问题是,您通常不会为自己设计类层次结构,而是为团队中其他人或客户使用的包设计类层次。由于子类仍然有这种方法,不可避免地会有人尝试使用它
对于现实世界中的问题,这可能会导致你不希望发生的数据操纵。举个例子:有人可以让你的地面鸭子飞起来,但由于它不知道如何降落,它会在进食方面遇到问题。
它实际上是关于设计的,从用例和与其他实体的关系来看是有意义的。你真的能说一只不会飞的鸭子就是一只鸭子吗?
我认为,一开始就有一个错误的假设,那就是所有的鸭子都会飞,但在这里,有一种特殊的情况,鸭子实际上不能飞。但这个错误很容易犯,因为鸭子会飞的假设是正确的。
但如果我不从Child类调用Fly((,那也没关系。那为什么这本书说它不好呢?
这通常不是一个伟大的设计,因为它给人一种错误的想法,认为它可以像其他鸭子一样飞行。这并不是说这是错误的——你可以让它发挥作用,只是不理想,必须在你的文档中明确表示,Fly对那种特定类型的Duck没有任何作用。
构图胜过继承。。。继承和组合肯定有好的一面和坏的一面——在某些情况下需要继承,即依赖注入——因此是行为的概括。
在这种情况下,你可能想要继承,这样你就可以迭代鸭子,让它们嘎嘎作响或一起飞翔,而不需要知道鸭子集合中有什么类型的鸭子。
与此问题相关的同一领域中的问题:https://softwareengineering.stackexchange.com/questions/75189/why-avoid-java-inheritance-extends