策略模式

时间:2022-11-29 22:06:36

风带着万物全部飘散,可那飘散的不是万物,而是我的青春

一个简单的鸭子应用

策略模式

使用简单继承的模式实现多个不同的鸭子实现类。

新需求 - 鸭子能飞

现在公司为了甩开其他竞争者,需要在应用中添加一个新需求,得让鸭子飞起来。

策略模式

如图所示,只要在鸭子类中新增一个让鸭子飞的方法即可。

可怕的事情发生了

在公司的应用演示会上,有很多橡皮鸭在屏幕上飞来飞去。

策略模式

造成这种问题提出现的原因:并不是所有的鸭子都会飞。

提示:

  • 对代码所做的局部修改,影响层面可不只是局部(会飞的橡皮鸭)

  • 当涉及到代码的维护时,为了“复用”代码而使用继承,结局并不完美

利用接口改造代码

fly()方法从超类中去取出来,放进一个Flyable接口,只有会飞的鸭子去实现这个飞的接口。

策略模式

这种接口改造,确实可以实现目的。但是太笨了,完全不利于代码的复用与维护。如果项目后期需要你修改一下鸭子的飞行动作,那么你是不是要去每一个鸭子的实现类里进行修改。

Java接口不具有现实代码,所以继承接口无法实现代码的复用。这意味着:无论什么时候你需要修改其实现类的某一个行为,你都要往下追踪到实现类中进行修改。

我们要做的应该是,花费较少的时间重做代码,而让程序去做更酷的事情。

把问题归零

上面问题出现的原因,就是新需求来的时候,我们改动了系统中的某些部分,导致系统其他部分受到了影响。

所以,要解决这个问题,就是要把这两部分分开,各自封装处理。

即:

设计原则:

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

分开变化和不会变化的部分

找出应用中哪些是不变的,哪些是变化的。

不变的:swim()display() 基本上所有的鸭子都会

会变化的:fly()quack() 不同的鸭子叫声和飞行动作不一样

把这两部分分开

策略模式

设计鸭子的行为

好的代码是非常有弹性的,就是因为一开始代码没有弹性,才走到现在这条路上的。

如果我们现在需要生产一个新的绿鸭头实例,并指定特定“类型”的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,这样就可以在“运行时”动态地“改变”绿鸭头的飞行行为。

要实现这个目标,就需要用到下面的设计原则:

针对接口编程,而不是针对实现编程。

  • 以往的做法:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑定死死的,要更改行为,就要去实现类里进行代码的修改。

  • 针对接口编程:Duck的子类将使用接口(FlyBehaviorQuackBehavior)所表示行为,所以实际的“实现”不会被绑死在鸭子的子类中。(换句话说,特定的具体行为编写在实现了FlyBehaviorQuackBehavior的类中)。

    • 这样做的好处,后面新增一些新的行为,就不会影响到之前的代码,只需要在接口上进行扩展即可。而且这种方式,鸭子类就不再需要知道行为的具体实现细节,只需要调用接口的方法即可。

整合鸭子的行为

关键在于,鸭子现在会将飞行和呱呱叫行为“委托”别人处理,而不是使用定义在Duck类或其子类的呱呱叫和飞行行为。

策略模式

每只鸭子都会引用实现QuackBehavior接口对象,鸭子对象不亲自处理呱呱叫和飞行行为,而是委托给代理接口去处理。

封装行为的大局观

核心:不再把鸭子的行为说成是“一组行为”,现在开始把行为想成是“一族算法”。

策略模式

“有一个”可能比“是一个”更好

每一个鸭子都有一个FlyBehavior和一个QuackBehavior,好将飞行和呱呱叫行为委托给它们代为处理。当你将两个类结合起来使用,这就叫组合。

这种组合做法与“继承”不同的地方在于,鸭子行为不是继承来的,而是和适当的行为对象“组合”来的。

这就是第三个设计原则:

多用组合,少用继承。

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

总结

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