《HeadFirst设计模式》第一章策略模式-读书笔记

118 阅读7分钟

1. 前言

工作以后,代码不单是写完就可以的,复用,维护也是很重要的,以前自己写代码都是以功能为目的,不考虑其他的只在一个类里全部写完就行,但这样以后会暴露出更多的问题,与其乱写,不如不写,多留一些时间去构思整个流程,而不是说干就干。

设计模式是很早之前就接触到的概念,但出于用不到就不学的观念,一直都没有去认真的学习,这次下定决心去学一遍设计模式和开发原则,,打好基础,提高思维的高度。

2. 从鸭子说起

如果说《机器学习》是西瓜书,那设计模式第一章就是鸭子书,以物为例让人留下更加深刻的印象。

假设我们有一群鸭子类,它们都是继承Duck这个超类。

Duck
quack()
swim()
display()
//鸭子的其他方法

所有的鸭子都会呱呱叫(Quack)也会游泳(Swim),所以由超类负责处理这部分的实现代码。

因 为 每 一 种 鸭 子 的外 观 都 不 同 , 所 以display()方法是抽象的,每个鸭子子类负责实现自己的display。

这样仿佛是一套完美的系统,大家各司其职,各做各的,但相同的部分由超类负责管理。

3.鸭子要飞了

新的需求来了,这时候我们要加入鸭子会飞的功能。鸭子本来有翅膀,没问题,在超类里加入一个fly方法,让所有的子类继承就好了,

Duck
quack()
swim()
display()
fly() //新加入的飞行方法
//鸭子的其他方法

确实,所有的鸭子都会飞了,然而系统里还有一些橡皮鸭子,它们也会飞了,这是不符合常理的!!!

我们忽略了一件事情,并不是所有子类都会飞,某些不会飞的也继承了此方法。

注意:对代码所做的局部修改,影响层面可不只是局部。

当涉及"维护"时,为了"复用"目的而使用继承,结局并不完美。

3.1 第一个解决办法—子类覆盖

我们可以把橡皮鸭中的fly方法覆盖掉

RubberDuck
quack(){//吱吱叫}
display(){//橡皮鸭}
fly(){
//覆盖,什么也不做
}

这样好像确实解决了橡皮鸭的问题,但如果我们以后每次加入不会飞的鸭子,都要这样检查一遍,就失去了复用的意义了。

3.2 第二个解决办法---利用接口

我们可以把fly方法从超类中取出来,放进一个Flyable接口中,这样一来,只有会飞的鸭子才实现此接口。

是不是感觉这个办法好像可以,完全不是!这么一来重复的代码会变多,如果覆盖几个方法就算是差劲,那么对于48个Duck的子类都要稍微修改一下飞行的行为,简直就是噩梦。

如果能有一种建立软件的方法,好让我们可以用一种对既有的代码影响最小的方式来修改软件该有多好。我们就可以花较少时间重做代码, 而多让程序去做更酷的事。

4. 重新看待问题

现在我们知道使用继承并不能很好地解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的。Flyable与Quackable接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才继承Flyable),但是Java接口不具有实现代码,所以继承接口无法达到代码的复用。这意味着: 无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!

幸运的是,有一个设计原则,恰好适用于此状况。

设计原则: 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。 下面是这个原则的另一种思考方式:“把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。 这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。 好,该是把鸭子的行为从Duck类中取出的时候了!

5 重新设计鸭子

我们提取出两组类,一个是fly,一个是quack,每一组类实现各自的动作,可以指定特定类型的飞行行为给鸭子,让它们动态的去改变就好了。

设计原则:针对接口编程,而不是针对实现编程。

我们利用接口代表每个行为,行为的每个实现都将实现其中的一个接口。

这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑得死死的,没办法更改行为(除非写更多代码)。在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。(换句话说,特定的具体行为编写在实现了FlyBehavior与QuakcBehavior的类中)。

image-20190922172247753

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了,不只是为Duck类而提取,现在与Duck无关了。

这么一来,有了继承的“复用”好处,却没有继承所带来的包袱。

6. 实现鸭子行为

首先,在Duck类中“加入两个实例变量”,分别为“flyBehavior”与“quack Behavior”,声明为接口类型(而不是具体类实现类型),每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型(例如:FlyWithWings、Squeak等)。 我们也必须将Duck类与其所有子类中的fly()与quack()删除,因为这些行为已经被搬到FlyBehavior与QuackBehavior类中了。 我们用两个相似的方法performFly()和performQuack()取代Duck类中的fly()与quack()。

超类

Duck
FlyBehavior flyBehavior
QuackBehavior quackBehavior
performQuack(){ quackBehavior.quack(); }
swim()
display()
performFly()

某个子类

public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println(“I’m a real Mallard duck”);
}
}

7. 总结

当你将两个类结合起来使用,如同本例一-般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。

如你所见,使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

设计原则:多用组合,少用继承。

策略模式

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。