策略模式和命令模式

策略模式和命令模式

策略模式和命令模式实现上看起来有些相像,都涉及到接口的定义和实现该接口的具体类。

比如在开发一个游戏,我们有很多种单位,比如步兵、矮人火枪手或者迫击炮小队,每个单位都有一个攻击的动作,但是具体的实现不同,比如步兵是近战攻击,火枪手有远程攻击。在这种情况下,每个单位都有自己的攻击方式,但它们都遵循相同的攻击接口。这个时候,我们可以采用策略模式,去定义一个攻击的接口,然后让子类具体实现这个接口。这种模式的好处就是可以轻松地添加新的新的单位类型及其攻击方式,还可以在运行时动态更改单位的攻击行为,而不影响其他代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 定义攻击行为接口
class AttackBehavior:
def attack(self):
pass

# 实现不同的攻击策略
class MeleeAttack(AttackBehavior):
def attack(self):
print("Performing a melee attack.")

class RangedAttack(AttackBehavior):
def attack(self):
print("Performing a ranged attack.")

class ArtilleryAttack(AttackBehavior):
def attack(self):
print("Performing an artillery attack.")

# 单位类
class Unit:
def __init__(self, attack_behavior):
self.attack_behavior = attack_behavior

def perform_attack(self):
self.attack_behavior.attack()

# 创建不同类型的单位
infantry = Unit(MeleeAttack())
dwarf_gunner = Unit(RangedAttack())
mortar_squad = Unit(ArtilleryAttack())

# 使用不同的攻击方式
infantry.perform_attack() # 输出: Performing a melee attack.
dwarf_gunner.perform_attack() # 输出: Performing a ranged attack.
mortar_squad.perform_attack() # 输出: Performing an artillery attack.

科学计算程序中会有很多地方能用到策略模式,因为很多工作就是为同一个问题开发不同的算法。比如不同的物理问题可能需要不同的数值离散化方法,通过策略模式可以定义一个通用的离散化接口,为每种离散化方式实现具体策略;比如不同的边界需要设定不同的边界条件处理方式,可以为每种边界条件定义一个策略,要设置不同的边界条件时直接安排不同的策略对象就可以了;或者不同的问题可能适合不同的求解算法,通过策略模式可以做灵活的算法切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BoundaryStrategy(ABC):
"""Boundary strategy abstract class."""

@abstractmethod
def handle(self, surface, phonon):
"""process phonon.

According to the surface geometry, change properties of
the injected phonon.

Args:
surface (Surface): Surface objective.
phonon (Phonon): Phonon objective.
"""
pass

DiffusiveStrategy()
ReflectiveStrategy()
GrayDiffusiveTransReflectStrategy(transmisstance = 0.5)

现在有另外一种情景,比如对于战棋类SRPG游戏,像火焰纹章这类,一个单位可能在一个回合中连续进行多个操作,比如先给自己上一个BUFF,然后移动到某个地方,然后执行攻击操作,没想好的话还可以撤销某些操作,最后都决定完了点击按钮顺序执行。这个流程,并不是对同一个需求的不同实现,而是在命令层面去统一不同操作的共同性,使得我们可以对这些不同的命令用同一个接口去做一些事情,比如对请求排队、记录日志、或者撤销操作等等。

读这些总让我想起些其他的事情,在海德格尔的世界里,“世界”并不是一个外在于人的物理空间,世界乃是与他内在一体的意义结构。比如山岩、高楼,当无人站入其中时,它们尚不是“世界”,而仅只是无意义的“物”,一旦某人站入其中,他们就成了世界,成为有意义的相关者。人具有能动性,因而具有自由,他参与了自己所处“世界”的构建。做科研和工作时间越长,感觉自己的怀疑论倾向就更多一点,现象就这样发生了,不同的人在这个现象上面建了一个又一个框架,叠了一层又一层知识试图去解释这个现象,但都好像是空中楼阁。大厦建的越高,好像离地下的真相越远。自己要是个马克.思主义者,那就会用阶.级去对这个世界建模和划分,而要是个佛教徒,对世界运转的图像就完全不同的,比如众生无止息地造业,有因就有果,善业必有善报,恶业必有恶报,谓之业力与因果。众生共同的业力,形成了世间... 哪里也没有真正的图像,就像霍金的金鱼缸一样,意义和价值判断都可能是虚假的。所以只值得为了具体的人去放弃概念,而不值得为了抽象的干净的概念去牺牲掉人。所以放弃在意义幻想的诱饵中获得充实感,忍受西西弗斯式的痛苦,"假如他每走一步都有成功的希望支持着,那他的苦难又在何方呢?"

村上 所谓“进入匣子”,在宗教那里,就是“绝对皈依”吧?

河合 是的,绝对皈依。这个说舒心也舒心。看见这些人,就会对世界怀有疑问,觉得“这好像有些怪”,全都。而“有些怪”这个疑问,一旦进入匣子,就全部被解释得十分通透:“这是karma(业障)”。

村上 解释通透这点,对个人是很重要的。

河合 不错。不过么,全都解释通透的逻辑,那东西是绝对不成立的。让我们说来就是这样。可是,普通人喜欢解释通透的东西。

村上 是啊,大家都在寻求那样的东西。这不限于宗教,一般媒体也是那样。”

“不过,由于全力以赴,可以从中得到充实感。教团方面也巧妙利用了这一点。修行也是同样,还是把某种程度的程序完成了更能得到充实感。对奥姆来说,充实感是个诱饵。所以才提供剧烈的修行。修行越是剧烈,从中得到的充实感越大。”

说的粗暴点,社会原本就是恶劣的东西。可是不论如何恶劣,我们(至少是我们中的绝大多数)却不得不在其中苟活下去——尽量诚实地,正直地。重要的真是毋宁在于这一点。 说得再深入些,我认为这里的外在混沌,不应当作他者和障碍加以排斥,而应是为我们内在混沌的反应接纳下来。其间的矛盾、庸俗、伪善和软弱,其实不正与我们内心暗藏的矛盾、庸俗、伪善和软弱相同吗?

No Silver Bullet,软件工程可以使人的想法更立体一点,大家都学习一些设计模式,世界一定会更平和一点..

在命令模式的场景下,我们把这些操作都抽象为一个命令对象,比如

1
2
3
4
5
6
7
class Command:
def execute(self):
pass

def undo(self):
pass

接下来我们用这个命令类提供的统一接口,去开发控制器类,控制器类只依赖于抽象,而不依赖于命令类的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Controller:
def __init__(self):
self.history = []

def execute_command(self, command):
command.execute()
self.history.append(command)

def undo_last_command(self):
if self.history:
command = self.history.pop()
command.undo()
else:
print("No command to undo")

现在我们实现两个具体的基本命令,移动和攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MoveCommand(Command):
def __init__(self, unit, new_position):
self.unit = unit
self.new_position = new_position
self.previous_position = None

def execute(self):
self.previous_position = self.unit.position
self.unit.position = self.new_position
print(f"{self.unit.name} moved to {self.unit.position}")

def undo(self):
self.unit.position = self.previous_position
print(f"{self.unit.name} moved back to {self.unit.position}")

class AttackCommand(Command):
def __init__(self, unit, target):
self.unit = unit
self.target = target

def execute(self):
print(f"{self.unit.name} attacked {self.target.name}")

def undo(self):
print(f"Undo {self.unit.name}'s attack on {self.target.name}")

顺手创建一个单位类

1
2
3
4
5
class Unit:
def __init__(self, name, position):
self.name = name
self.position = position

于是我们就可以创建一个单位,依次执行多条命令,并且还可以进行撤销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建单位
unit1 = Unit("Knight", (0, 0))
unit2 = Unit("Archer", (1, 1))

# 创建控制器
controller = Controller()

# 执行命令
controller.execute_command(MoveCommand(unit1, (2, 2)))
controller.execute_command(AttackCommand(unit1, unit2))

# 撤销最后一个命令(攻击命令)
controller.undo_last_command()

# 再次撤销(移动命令)
controller.undo_last_command()

命令模式还可以结合策略模式,比如像上面一样把attack攻击封装成一个策略,然后骑士和Archer分别具体实现这个策略,而这种变化在AttackCommand这里是无法感知的,因为问我们只依赖于接口,而没有依赖于具体的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# 策略模式的代码,AttackBehavior类定义Attack的接口,所有与Attack有关的操作,都要仅仅依赖于这个接口,而不要去涉及任何一点的实现
class AttackBehavior:
def attack(self):
pass

# 具体实现攻击类,这些具体实现类,在类之间的关系中是不可见的,他们彼此之间应该是完全可以替换的
class MeleeAttack(AttackBehavior):
def attack(self):
print("Performing a melee attack.")

class RangedAttack(AttackBehavior):
def attack(self):
print("Performing a ranged attack.")

class ArtilleryAttack(AttackBehavior):
def attack(self):
print("Performing an artillery attack.")

# 定义一个单位类,单位与攻击类,仅用接口的抽象类构建联系
class Unit:
def __init__(self, attack_behavior):
self.attack_behavior = attack_behavior

def perform_attack(self):
self.attack_behavior.attack()

# 命令模式部分,同样任何与Command打交道的部分,都只能看到Command类提供接口的行为,Command的具体实现类是不可见的
class Command:
def execute(self):
pass

def undo(self):
pass

# 移动命令
class MoveCommand(Command):
def __init__(self, unit, new_position):
self.unit = unit
self.new_position = new_position
self.previous_position = None

def execute(self):
self.previous_position = self.unit.position
self.unit.position = self.new_position
print(f"{self.unit} moved to {self.unit.position}")

def undo(self):
self.unit.position = self.previous_position
print(f"{self.unit} moved back to {self.unit.position}")

# 攻击命令,接受一个单位作初始化
class AttackCommand(Command):
def __init__(self, unit):
self.unit = unit
self.previous_action = "Idle"

def execute(self):
self.previous_action = "Attack"
self.unit.perform_attack()

def undo(self):
print(f"Reverted {self.unit}'s action from Attack to {self.previous_action}")

# 控制器类,按照Command类的接口,维护命令们的历史记录
class Controller:
def __init__(self):
self.commands = []
self.history = []

def add_command(self, command):
self.commands.append(command)

def execute_commands(self):
while self.commands:
command = self.commands.pop(0)
command.execute()
self.history.append(command)

def undo_last_command(self):
if self.history:
command = self.history.pop()
command.undo()
else:
print("No commands to undo")

现在可以创建具有不同攻击模式的单位,执行命令,并撤销他们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建单位
infantry = Unit(MeleeAttack())
infantry.position = (0, 0) # 初始位置
dwarf_gunner = Unit(RangedAttack())
dwarf_gunner.position = (1, 1) # 初始位置

# 创建控制器
controller = Controller()

# 添加并执行命令
controller.add_command(MoveCommand(infantry, (2, 2)))
controller.add_command(AttackCommand(dwarf_gunner))
controller.execute_commands()

# 撤销最后一个命令(攻击命令)
controller.undo_last_command()

# 再次撤销(移动命令)
controller.undo_last_command()

在科学计算中,想要记录用户的每一步命令,打印日志,或者撤销上一步操作,也可以用同样的模式去设计。策略模式是专注于具体实现的相互替换,而命令模式是一个更高层次的抽象,他仅关注于如何封装和管理操作,而不关注操作的具体实现细节。