鸭子例子的策略模式 - 《Head First设计模式》

11

我想询问一下关于这本书里的鸭子例子,让我感到困惑并感觉有矛盾。

  1. 问题 enter image description here

  2. 结论 enter image description here

他说:“当乔向鸭子超类添加新行为时,他同时也添加了一些不适合某些鸭子子类的行为”。

但是在结论中,他添加了performFly()performQuack();。这有什么不同?因为我认为它与他同时也添加了一些不适合某些鸭子子类的行为相同?

**图片摘自《Head First设计模式》。 ** 这个问题并没有说明这本书不好,在我看来这本书非常好。这只是我在询问我没有从书中理解的内容。


2
他们所做的只是将一个函数(quack)分离到一个单独的类中。这有点复杂。最好创建一个名为“FlyingDucks”的鸭子子类,然后将Fly()放在其中。然后,所有飞行的鸭子都将继承该类。或者,将Fly()定义为抽象的,强制每个子类定义飞行的含义。他们所做的更多、更复杂。每个子类都需要选择一个Fly类来分配给flyBehavior——即使它们不能飞。 - kainaw
1
我说“问题”和“结论”是矛盾的,这样说对吗?还是我不明白他的意思? - Kakashi
1
更好的设计是认识到,根据(虚构的)问题考虑鸭子的方式,橡皮鸭不是真正的鸭子——它不会飞,也不会游泳(除非你把漂浮算进去),等等。如果出于某种原因,您真的想要一个包含真正的鸭子和橡皮鸭的类,那么您应该在这些物品中寻找共同点作为共同属性;而不是从某些物品中的共同属性开始,并把其他物品强行适应它。 - arcy
1
需要注意的一点是,这是书中的第一章,为此解决方案可以使用的几个概念尚未介绍。很多时候他们会说“可能有更好的方法,并且他们稍后会解决这个问题。”(意译) - Todd Vance
4个回答

8
我不是设计模式的专家,但在阅读那本书时,我对该特定章节的第一印象是接口的构建和实现方式违反了一个众所周知的编程原则: 接口隔离原则 (ISP) 基本上这个原则声明:

没有客户端应该被强制依赖于它不使用的方法

因为有些不会飞的鸭子实现了fly()方法,即使它们不需要它。 也就是说,在这种情况下,无法避免地要实现所有接口方法,因为在客户端我们使用了多态行为,需要确保即使未使用也有所有方法可用。


5
策略模式适用于你更加偏爱组合而不是继承 http://en.wikipedia.org/wiki/Composition_over_inheritance
这是一个好的实践,因为你可以在不改变任何代码的情况下改变类的行为。而且你也不需要一个庞大的类树。你还可以动态地改变类的行为。
在例子中,它定义了父类中的“行为”。在父类中,你定义了鸭子可以有飞行行为和呱呱叫行为。但这并不意味着子类必须具有呱呱叫或飞行行为。
你可以拥有一只不会飞的鸭子,当你调用“fly”时,它什么也不会做,因为我们有一个“非飞行”行为。
与在类中硬编码鸭子的行为不同,你可以随时改变这只鸭子的行为。

2
在结论部分,他添加了两个具有fly()函数的新类。然而,这个函数并不总是让鸭子飞行。橡皮鸭不能飞行,因此它们使用FlyNoWay类的实例。其他可以飞行的鸭子使用FlyWithWings类的实例。在Duck类中的flyBehavior字段可能会在构造函数中设置。 performFly()函数将调用所选择的任何类的fly()函数。
正如评论中kainaw所说,这是一个相当复杂的解决方案。然而,它仍然可以使用。假设你正在创建一个鸭子设计程序。如果用户选择鸭子是否能飞,它就不能被硬编码。你可以创建一个布尔值,但你可能需要处理更复杂的情况,比如行为。你可能需要一个WildDuckBehavior类和一个DomesticDuckBehavior类,每个类都有自己的关于如何行动的信息。基本上,书中的例子是这种用法的简化版本。

3
但是橡皮鸭仍然继承了“飞”的属性,而橡皮鸭并不需要它? - Kakashi
@MuhammadRifkiMockie,我稍微误读了图表,所以我编辑了答案。这有帮助吗? - James Westman

2
你说得没错。书中提供的解决方案存在一个巨大的问题: "FlyNoWay"不是"FlyBehaviour"的子类情况。为了有任何意义,"FlyBehaviour"必须要求具有飞行能力。从它继承的类将指定行为(例如使用翅膀飞行)。子类不能比它继承的类更少。 仔细看一下,"FlyNoWay"只是一个伪类,引入了一种不适当的方法来解决多态性问题。 正确的方法是使用接口。
class Duck
{
    swim();
}

class MallardDuck : IFlyable
{
    fly();
}

class RedheadDuck : IFlyable, IQuackable
{
    fly();
    quack();
}

关于代码重用,你需要尽可能地使接口严格化,确保对接口进行的大多数更改都会导致程序无法编译。"最初的回答"。

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