本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题:
- 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的问题?
- 是什么?通过Python语言来去实现这个设计模式,用于解决为什么中提到的问题。
- 怎么用?理解了为什么我们也就基本了解了什么情况下使用这个模式,不过在这里还是会细化使用场景,阐述模式的局限和优缺点。
这一篇我们先来看看策略模式。
为什么?
假设我们有如下一个项目:
飞机大战游戏中,玩家可以选择A型飞机,也可以选择B型飞机,A型飞机可以发射穿甲弹,B型飞机可以发射速射弹
plane.png好了,初看上去是不是很简单,也很容易想到可以用继承来解决代码复用的问题,好吧,talk is cheap, show me the code!
# Plane作为所有种类的飞机的父类
class Plane(object):
def __init__(self):
self.color = black
self.life_left = 100
def fire():
pass
def turn(direction):
"""用于实现飞机的转向"""
...
有了父类我们可以考虑造A型飞机和B型飞机了
class ATypePlane(Plane):
def __init__(self):
super(ATypePlane, self).__init__()
def fire():
... #这里实现穿甲弹射击
class BTypePlane(Plane):
def __init__(self):
super(BTypePlane, self).__init__()
def fire():
... #这里实现速射弹射击
接下来你可能会在很多场景使用这两个类,如下
plane_a = ATypePlane()
...
# when role click fire button
plane_a.fire()
# when role click left button
plane_a.turn(left)
...
plane_b = BTypePlane()
...
但是某一天老板突发奇想,我们的生意不好,是不是因为我们的飞机种类太少了,玩家觉得没意思,对,就是这样,那个产品经理你过来,我们需要多加几个飞机类型供玩家选择,你觉得如何?产品经理拍拍胸脯,向老板保证,没问题啊,我现在就加,于是乎,于是乎身为程序员的你就需要苦逼的,写出十几种飞机类型的代码,你觉得还可以承受,不就是拷贝粘贴么?
好了,现在你有了十几种飞机
class ATypePlane(Plane):
....
class BTypePlane(Plane):
...
...
class MTypePlane(Plane):
...
可是有一天老板又突发奇想,为什么只能有速射弹和穿甲弹,为什么不多加几种,而且每过一关,都要升级子弹类型,而且还要在吃到升级奖励时能够马上升级子弹。产品经理拍手称快,大赞老板高明,并保证马上完成。这里程序员同学表示shit!可是大吼之后,还是要把活撸完啊,毕竟还要靠这微薄的薪水养家糊口。
拷贝粘贴大法虽好,但是此处似乎有些粗鲁,且也无法在运行时进行飞机行为的更改啊。更何况将来一旦又有更改,那么就需要所有的飞机类型都更改。我们总结一下我们面临的问题:
- 如果将来的fire行为有更改,我们需要更改所有涉及到的飞机类型
- 我们无法在运行时动态的更改某个飞机类型的fire行为
- 另外如果我们将来想让某些飞机没有fire行为,目前的继承方式是无法做到的
这就是我们面临的问题,那么到底该如何是好呢?软件设计的一个原则就是把变化抽离出来,单独封装。
那我们能不能把飞机的fire行为单独抽离出来呢?
答案是可以,这就是我们的策略模式了。
是什么?
简单来说,策略模式就是把变化的行为抽离出来,单独封装,使用的时候,直接调用这些封装好的行为即可。
以我们的游戏项目为例,我们可以把fire()行为拿出来
class FireAction(object):
def fire():
pass
class SpeedFire(FireAction):
def fire():
... #定义速射子弹
class ArmorPiercerFire(FireAction):
def fire():
... #定义穿甲弹行为
#可以定义更多的fire行为
...
现在我们需要在我们的Plane当中加入fire行为
class Plane(object):
def __init__(self, fire_action=None):
self.color = black
self.life_left = 100
self.fire_action = fire_action
def turn(direction):
"""用于实现飞机的转向"""
...
子类的实现如下:
class ATypePlane(Plane):
def __init__(self, fire_action=None):
if (fire_action is None or
(not isinstance(fire_action, FireAction))):
fire_action = ArmorPiercerFire() #默认使用ArmorPiercerFire
super(ATypePlane, self).__init__(fire_action)
def fire(fire_action=None):
if fire_action is not None and isinstance(fire_action, FireAction):
self.fire_action = fire_action
self.fire_action.fire()
#没有fire行为的飞机
class CTypePlane(Plane):
def __init__(self):
super(CTypePlane, self).__init__()
...
使用子类的程序可能如下:
plane_a = ATypePlane()
plane_a.fire() #这时使用默认的ArmorPiercerFire
#根据游戏的规则升级fire
plane_a.fire(SpeedFire()) #这时使用SpeedFire
现在我们来看下之前面临的问题是否解决了呢。
- 如果将来的fire行为有更改,我们需要更改所有涉及到的飞机类型
目前来看如果将来的某个fire行为需要更改,我们只需要修改特定的FireAction子类即可,不需要再更改所有的子飞机类型
- 我们无法在运行时动态的更改某个飞机类型的fire行为
我们可以根据需要在fire的时候动态的更改fire行为,只需要传递特定的FireAction类型即可
- 另外如果我们将来想让某些飞机没有fire行为,目前的继承方式是无法做到的
我们的CTypePlane已经没有fire行为了
这样看来我们之前面临的三个问题都通过策略模式解决了。而且将来如果新增策略(即fire行为)时,也无需担心之前的代码,只需要重新实现一个FireAction子类即可,那么下面让我们总结下什么情况下使用策略模式吧。
怎么用?
通过我们之前的飞机游戏的例子我们可以看出当出现如下情况时有必要采用策略模式:
什么情况下使用
- 当一个类型可能会有很多种子类,且这些子类的某些行为相同,某些行为不相同
- 子类的行为需要在运行时更改
这时就有必要采用策略模式将不同的行为定义成不同的“策略”,同时通过组合的方式来使用这些“策略”,这也是设计原则之一,即能用组合就不要使用继承
什么情况下不用
学习设计模式最忌讳过度使用,所以如果当你确定不会有很多子类,也不需要动态更改行为时就不需要使用策略模式。这涉及另外一个设计原则,凡是不使用设计模式也能很好解决问题的时候就不要使用设计模式,这个是我说的。
好了,现在看官已经看懂了策略模式,如果看官觉得不错,就请点个赞鼓励下作者,好让我能鼓起精神写完这个系列。
网友评论