我正在阅读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();
答案 0 :(得分:2)
如果子类没有与父类共享一个或多个特性,那么有什么理由使它从父类继承?
继承背后的想法是基类的属性和方法必须由其所有子级共享。除此之外,每个孩子可能拥有自己的专用属性和方法,并覆盖继承的方法。在您的情况下,Duck
可以Fly()
,所有从中继承的实体都必须。仅仅因为你没有对孩子说Fly
并不意味着孩子不“知道”如何Fly()
。
实际上,这可能是使用界面的好方案。此接口将具有您的类可以实现的方法Quack()
。然后,虽然它与Quack
共享Duck
的能力,但它无法Fly()
,因此不会继承它,同时仍保留一些共享功能。
为了正确看待这一点,假设你有一个Whistle
也可以Quack
,但不能Fly
。由于在Fly
上没有调用CantFlyButQuackDuck
似乎是合理的,因此在Whistle
上它应该同样合理,除了显而易见Whistle
绝不是明显的一种Duck
。这意味着你应该寻找的是分享某些属性/方法,而不是暗示你的世界模型中的“X是Y”关系。
答案 1 :(得分:2)
因为您通常构建类的方式是从最少的功能到大多数功能,这可以防止意外的功能被转移。
你通常采用的方式是这样的:
class Bird
{
public void MakeSound()
{
// insert sound related code here
}
}
然后根据需要继承
class Duck : Bird
{
public void Fly()
{
// insert flight code here
}
}
class FlightlessDuck : Bird {}
这样做你绝对肯定那只不会飞的鸭子不会飞,而鸭子则是。这两只鸭子都能嘎嘎叫(发出声音),但其中只有一只能够飞行。
以另一种方式从基类中携带Fly
方法,所以即使你没有在子类中实现任何代码,你仍然可以调用它,可能破坏一切。
更理想的是,您将添加一个包含所有与飞行相关的方法的界面,因此您可以确保所有需要飞行的鸟类遵守相同的标准。但这超出了你的问题的范围。
答案 2 :(得分:2)
就像给孩子送糖果并告诉孩子不要吃它!但是,嘿,孩子仍然可以(很可能会有机会在那里:D)!!
实际上:
在行业标准中,您通常应该处理其他人编写的代码或其他人应该在您的代码上工作的代码。你不能假设永远不会做某件事!
从根本上说:
这听起来甚至荒谬!你告诉孩子班级嘿,这是你的父母,这是你父母允许你做的事情。现在就像一个真诚的孩子,你选择不使用这些功能之一...但你仍然可以。即使它不应该飞行,鸭子也能飞行!
代码:
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!
答案 3 :(得分:2)
您有三个选项可覆盖Fly
。
这取决于你的情况。您是否想稍后想知道为什么某只鸭子在你告诉它时不会飞?或者,当你试图让某只鸭子飞起来的时候,你是否希望这个程序能够倒下?
在实践中,我们宁愿有另一种解决方案:
目前,您的基类认为这两种方法具有凝聚力。如果后代实现了一个,则必须实现两者或保持基本行为。所以要解决这个问题,我们可以segregate your interfaces成为有凝聚力的单位:
interface IFly {
void Fly();
}
interface IQuack {
void Quack();
}
或我们可以稍后在树中添加fly
方法来解决:
class QuackingDuck
{
public void Quack()
{
}
}
class FlyingDuck : QuackingDuck
{
public void Fly()
{
}
}
class CantFlyButQuackDuck : QuackingDuck
{
}
或者你可以将两种技术结合起来。
答案 4 :(得分:1)
您是否选择拨打Fly()
,可以调用(错误),将成为所有CantFlyButQuackDuck
的成员对象,尽管这个成员在一个不能(也不应该)对它做任何事情的对象的上下文中没有位置。
类似的提问可以适用于许多OOP原则。这只是一种组织方式,并以可扩展且有意义的方式对代码进行分解。如果按照您的方式扩展Duck
,您的代码将编译并运行正常,但您将在以后遇到问题。
答案 5 :(得分:0)
问题在于,您通常不会为自己设计类层次结构,而是为您团队中的其他人或客户使用的包设计。由于儿童班仍然采用这种方法,因此不可避免地会尝试使用它。
对于现实世界的问题,这可能导致您不希望发生的数据操作。 例如:有人可以让你的地面鸭子飞起来,但由于它不知道如何着陆它会导致喂养问题。
答案 6 :(得分:0)
在设计中,它的用例和它与其他实体的关系非常有意义。你真的可以说一只不能飞的鸭子是一只鸭子吗?
我认为开始时有一个错误的假设 - 这就是所有鸭子都可以飞行,但在这里,有一个特殊情况,鸭子实际上不会飞。但是这个错误可以很容易地做出,因为鸭子可以飞行的假设是合理的。
但是,如果我不从Child类调用Fly(),那没关系。那么为什么这本书说它不好呢?
它通常不是一个伟大的设计,因为它给出了错误的想法,它可以像所有其他鸭子一样飞行。这并不是说它是错的 - 你可以让它工作,它只是不理想,并且必须在你的文档中明确表示Fly不会对那种特殊类型的Duck做任何事情。
继承上的构成会浮现在脑海中......对于继承和组合来说,肯定有上下两侧 - 以及需要继承的某些情况,即依赖注入 - 所以行为的概括。
在这种情况下,你可能想要继承,这样你就可以遍历鸭子并使它们嘎嘎叫或一起飞行而不需要知道鸭子集合中有什么类型的鸭子。
与此问题相关的同一域中的问题:https://softwareengineering.stackexchange.com/questions/75189/why-avoid-java-inheritance-extends