《Head First 设计模式》思考笔记

CodeingBoy 7月 30, 2017

最近开始了对“设计模式”的学习和探索,我选择了《Head First 设计模式》作为设计模式的入门书籍,而四人组的《设计模式》则被留作进阶之用。

这篇文章记载了我在阅读《Head First 设计模式》过程之中所做出的一点思考。

策略模式

鸭子模拟器设计的演变和解决的问题

在书中,鸭子模拟器经历了数次改变:

初始设计是:Duck 类作为基类,定义了 quack 等一系列具体实现类应有的方法(以抽象方法的形式在基类定义)。但当 Joe 想要给所有 Duck 具体实现类添加 fly 方法时,他选择了在基类中编写 fly 方法而不是将其作为抽象方法交由子类实现,造成了“橡皮鸭子也会飞”的问题。

这种方式的优点在于:当确实所有子类都共享一个实现的时候,在基类中添加实现能够节省很多的精力。缺点则是当实现不是子类想要的实现时,会出现不愿意看到的后果。当然,子类可以通过 override 方法来进行重写,但需要付出巨大的成本(想一想,如果有 48 个 Duck 类但其中有 20 个子类是不能飞的,光是找到这 20 个类并且用一个空方法重写它就很麻烦)。

第二种设计是:Joe 提出了使用接口分离 quack、fly 等方法接口,再由子类对接口进行实现。这个办法的优点在于子类可以灵活决定自己的类型——既然我不能飞那我还实现 Flyable 接口干嘛?在判断实例类型时也可以通过 instanceof 来判断实例类型。但缺点在于接口与实现为紧耦合——由于接口实现方法中为具体的实现代码。除非有中间层(比如将相同的代码放置到某个方法中,所有用到这种代码的实现中替换为对该方法的调用),否则不能实现代码复用。

第三种设计则是策略模式:将“行为”抽象为类或接口,通过运行时动态创建或传入“行为”对象,并将事务委托给“行为”实现代为处理。这种方式引入了“行为”接口作为中间层,同时又通过多态来进行代码复用。并且可以通过在运行时替换“行为”实现来切换实现。Dependency Injection 就是策略模式的一种体现。

其实我自己还有想过别的方法,比如,Duck 类下再做抽象类,比如不能飞的鸭子可以继承于 FlyNoWayDuck,这个类不仅继承了 Duck 类并且用空方法重写了 fly 方法。这样子也可以将不变的代码与改变的代码分开出来。但这个方法,一方面无法将“行为”封装为算法族,因而其它不是继承与该类的实例(比如鸭鸣器)将无法复用代码;另一方面无法像策略模式一样在运行时动态改变实现。

“多用组合,少用继承”???

我理解“多用组合”是因为组合的灵活性,组合确实是个好东西。但是组合的松耦合有时说不定不是件好事,我制定了一系列抽象方法,强制要求子类实现它们,不需要担心“行为”对象为空的情况。

在这个问题上我还是很矛盾的,于是我决定在下面列出使用(我认为的)继承的好处和反驳:

  • 类接口存在于类体系结构中清晰可见,而不是定义在“行为”接口中——现代 IDE 看个东西按个鼠标就行
  • 集成度较低,“行为”接口中无法访问类的属性——好像确实是个问题,一个变通的方法是通过参数访问

暂时只想到这么多,“少用继承”我还是持个保留意见好了。继承还是组合要根据实际情况来选择。

 

本文采用 CC BY-NC-SA 3.0 协议进行许可,在您遵循此协议的情况下,可以自由共享与演绎本文章。
本文链接:https://blog.codeingboy.me/reflections-of-head-first-design-pattern/

发表评论

电子邮件地址不会被公开。 必填项已用*标注