Python中的策略设计模式

389 阅读7分钟

当你在开发软件应用程序时,你可能会遇到一些问题,实现你需要的功能。软件设计模式是在使用面向对象设计开发软件应用程序时,对其中一些经常发生的问题的典型解决方案。在这篇文章中,让我们了解一下流行的设计模式之一,策略模式,以及它在Python中的实现。

简介

在深入了解策略模式之前,你应该熟悉面向对象编程(OOP)的一些基本概念。设计模式的整个概念是围绕着对象展开的。设计模式是对经常发生的问题的高级解决方案。它们就像解决一个具体问题的蓝图。它们并不局限于单一的编程语言。你可以在任何支持面向对象编程的编程语言中使用设计模式;过程将是相同的,而语法会改变。 design patterns 有几种类型的设计模式,包括创造型结构型行为型。创造性模式是关于创建对象的不同方式,以增加我们代码的灵活性。结构模式是关于对象之间的关系,使用对象和类使更大的结构变得灵活。行为模式是关于对象之间的有效沟通和互动。

策略模式

策略模式是一种设计模式,它使我们的应用程序能够在运行时选择算法,使我们的应用程序变得灵活。GoF编写的关于设计模式的原版书中指出:"策略模式意在定义一个算法系列,封装每一种算法,并使它们可以互换"。更具体地说,它让你定义一组算法,这些算法在运行时可以根据一些因素进行互换。策略模式属于行为设计模式的范畴,因为它使算法的行为在运行时被选择。

使用方法

在开发软件应用程序时,你可能有几个选择来完成你的代码中的一些事情。根据你的客户选择、数据源或其他因素,你想在不改变代码的情况下做一些不同的事情。你常常倾向于在代码的主类中使用条件语句来定义不同情况的算法。但这并不是一种写出更好代码的优雅方式。它使你的代码的主类变得相当长,而且变得太难维护应用程序。

在这样的情况下,策略模式是一个理想的解决方案。策略模式建议你为你的不同情况的算法定义类,称为策略。策略在主类中被引用,称为上下文,代码根据该情况工作。上下文并不为该情况选择合适的策略。相反,客户端会将所需的策略传递给上下文。

例如,如果你有一个国际象棋应用程序,你可以在简单、中等或困难之间选择难度等级。计算机会根据你选择的级别选择一种算法。这是使用策略模式的最佳例子之一。

策略模式遵循开放/封闭原则;一个软件应用程序对扩展是开放的,但对修改是封闭的。这意味着你可以在不修改主类的情况下添加任何数量的额外策略。它使你的代码更加灵活,易于维护。

UML图

下面是策略模式的UML图。

UML Strategy

  1. Context- 它是我们应用程序的主类。它维护对一个具体策略的引用。
  2. 策略- 策略接口对所有支持的策略来说是通用的。Context只能通过策略接口与其他策略通信。
  3. ConcreteStrategies- 这些是使用策略接口实现算法的类。

实施

让我们看看实现策略模式的分步过程。

  1. 你应该首先确定你想在主类中作为具体策略执行的算法。
  2. 定义上下文(主类),并添加一个对策略的引用,一个设置策略的方法,以及另一个执行策略的方法。你也可以定义一个默认策略,只有在不喜欢默认策略的情况下才切换策略。
## context - the primary class
class Context:
    strategy: Strategy  ## the strategy interface

    def setStrategy(self, strategy: Strategy = None) -> None:
        if strategy is not None:
            self.strategy = strategy
        else:
            self.strategy = Default()

    def executeStrategy(self) -> str:
        print(self.strategy.execute())

首先,我们定义了strategy 字段,用于存储对策略对象的引用,以及两个方法,setStrategyexecuteStrategysetStrategy ,如果用户选择了一个选项,则设置所选择的策略,否则就是 default一个。

  1. 定义策略接口,这是所有具体策略所共有的。Strategy 接口有一个抽象的方法,你可以在具体策略中改变这个方法。
from abc import ABC, abstractmethod

## Strategy interface
class Strategy(ABC):
    @abstractmethod
    def execute(self) -> str:
        pass
  1. 定义具体策略,这些策略应该实现Strategy 接口。这些具体策略必须有一个共同的方法,该方法重写了Strategy 接口的execute 方法。
## Concrete strategies
class ConcreteStrategyA(Strategy):
    def execute(self) -> str:
        return "ConcreteStrategy A"

class ConcreteStrategyB(Strategy):
    def execute(self) -> str:
        return "ConcreteStrategy B"

class Default(Strategy):
    def execute(self) -> str:
        return "Default"
  1. 现在,用户可以在运行时选择他们想要的策略。创建一个上下文的对象,并传递一个具体的策略。
## Example application
appA = Context()
appB = Context()
appC = Context()

## selecting stratigies
appA.setStrategy(ConcreteStrategyA())
appB.setStrategy(ConcreteStrategyB())
appC.setStrategy()    ## sets to default strategy

## each object below execute different strategy with same method
appA.executeStrategy()
appB.executeStrategy()
appC.executeStrategy()

上述代码的输出将如下。

ConcreteStrategy A
ConcreteStrategy B
Default

如果你想使用另一个策略,用你想要的策略替换ConcreteStrategy实例。你可以添加一个新的具体策略,而无需改变上下文中的任何内容。

例子

让我们使用策略模式设计一个剪刀石头布的游戏。你可以在石头、布、剪子和随机中选择任何一种策略来与电脑对战。下面的示例代码使用策略模式来实现各种策略。

## Changing the strategy among Rock, Paper, Scissors, and Random

import random
from abc import ABC, abstractmethod

## Strategy interface 
class Strategy(ABC):
    @abstractmethod
    def selection(self) -> None:
        pass

## Concrete strategies
class Rock(Strategy):
    ## actual application will have the algorithm instead this method
    def selection(self) -> str:
        return "Rock"

class Paper(Strategy):
    def selection(self) -> str:
        return "Paper"

class Scissors(Strategy):
    def selection(self) -> str:
        return "Scissors"

class Random(Strategy):
    def selection(self) -> str:
        options = ["Rock", "Paper", "Scissors"]
        return random.choice(options)

## Context class
class Game:
    strategy: Strategy

    def __init__(self, strategy: Strategy = None) -> None:
        if strategy is not None:
            self.strategy = strategy
        else:
            self.strategy = Random()

    def play(self, sec) -> None:
        s1 = self.strategy.selection()
        s2 = sec.strategy.selection()
        if s1 == s2:
            print("It's a tie")
        elif s1 == "Rock":
            if s2 == "Scissors":
                print("Player 1 wins!")
            else:
                print("Player 2 wins!")
        elif s1 == "Scissors":
            if s2 == "Paper":
                print("Player 1 wins!")
            else:
                print("Player 2 wins!")
        elif s1 == "Paper":
            if s2 == "Rock":
                print("Player 1 wins!")
            else:
                print("Player 2 wins!")

## Example application
## PLayer 1 can select his strategy
player1 = Game(Paper())

# Player 2 gets to select
player2 = Game(Rock())

# After the second player choice, we call the play method
player1.play(player2)

根据两个玩家选择的策略,预期的输出将是。

Player 1 wins!

使用所有其他策略测试游戏的所有其他情况。为了给游戏增加额外的乐趣,可以尝试根据Lizard-Spock的扩展,在上面的例子中再创建两个策略。

总结

在这篇文章中,你已经看到了在哪里以及如何在你的代码中使用策略模式。你可以使用策略模式构建灵活的、可维护的软件应用程序。你可以在运行时根据用户的决定在不同的算法之间进行切换,而无需改变代码。但如果你的代码中只有几个算法,就没有必要使用策略。它只会让你的代码看起来很复杂,有许多类和对象。策略模式可以作为条件语句的替代品,用于选择应用程序的行为。但策略模式的潜在缺点是,用户必须知道策略之间有什么不同,才能选择他们需要的东西。因此,只有当应用程序的行为变化与用户有关时,你才最好使用策略模式。因此,请尝试使用策略模式使你的软件应用灵活。