掘金 后端 ( ) • 2024-04-22 09:24

[TOC]

设计模式学习(七)——《大话设计模式》

策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。

这种模式涉及到三个角色:

  • 环境类(Context):持有一个策略类的引用,最终给客户端调用。
  • 抽象策略类(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需实现的接口。
  • 具体策略类(ConcreteStrategy):包装了相关的算法或行为。

工作原理

  • 定义策略接口:首先,定义一个策略接口(Strategy),这个接口声明了算法或行为的方法。

  • 实现具体策略:接着,创建实现策略接口的具体策略类(ConcreteStrategy)。每一个具体策略类封装了一种算法或行为。

  • 创建环境类:然后,创建一个环境类(Context),它包含一个策略对象的引用。环境类提供了一个设置策略对象的方法(通常是构造器或者一个setter方法),以及将任务委托给策略对象的执行方法。

  • 客户端使用环境类:最后,客户端创建一个环境类的实例,并选择一个具体策略类传递给环境类。客户端通过环境类调用算法,而具体的算法则由具体策略类实现。

工作流程示例

假设有一个电商系统,需要根据不同的节日提供不同的打折策略。

定义策略接口:首先定义一个打折策略接口 DiscountStrategy,它有一个方法 applyDiscount。

interface DiscountStrategy {
    double applyDiscount(double price);
}

实现具体策略:然后实现具体策略类,例如 NoDiscountStrategy(无折扣)、ChristmasDiscountStrategy(圣诞节折扣)等。

class NoDiscountStrategy implements DiscountStrategy {
    public double applyDiscount(double price) {
        return price; // 不打折
    }
}

class ChristmasDiscountStrategy implements DiscountStrategy {
    public double applyDiscount(double price) {
        return price * 0.9; // 打9折
    }
}

创建环境类:创建一个环境类 PriceCalculator,它包含一个 DiscountStrategy 的引用。

class PriceCalculator {
    private DiscountStrategy discountStrategy;

    public PriceCalculator(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }
    
    public double calculatePrice(double price) {
        return discountStrategy.applyDiscount(price);
    }

}

客户端使用:客户端根据当前节日选择合适的打折策略,并使用 PriceCalculator 计算打折后的价格。

public class Main {
    public static void main(String[] args) {
        DiscountStrategy christmasDiscount = new ChristmasDiscountStrategy();
        PriceCalculator calculator = new PriceCalculator(christmasDiscount);
        

        double originalPrice = 100.0;
        double discountedPrice = calculator.calculatePrice(originalPrice);
        
        System.out.println("打折后的价格是:" + discountedPrice);
    }

}

工作策略模式的应用场景

当你想使用对象中的各种不同算法变体,同时希望能在运行时改变对象的行为时。 当你有很多类似的类,但它们在某些行为上有所不同时。 为了避免使用多重条件选择语句。不是使用条件选择来选择所需的行为,而是把这些行为封装在一个个独立的策略类中。

策略模式的优点

  • 策略模式提供了管理相关的算法族的办法。
  • 策略模式可以提供相同行为的不同实现。
  • 使用策略模式可以避免使用多重条件转移语句。
  • 策略模式提供了一种替换继承关系的方法。

策略模式的缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式会在程序中增加许多策略类或者策略对象。

示例代码(Python)

from abc import ABC, abstractmethod

class Strategy(ABC):
    @abstractmethod
    def do_algorithm(self, data):
        pass

class ConcreteStrategyA(Strategy):
    def do_algorithm(self, data):
        return sorted(data)

class ConcreteStrategyB(Strategy):
    def do_algorithm(self, data):
        return reversed(sorted(data))

class Context():
    def __init__(self, strategy: Strategy):
        self._strategy = strategy

    @property
    def strategy(self):
        return self._strategy
    
    @strategy.setter
    def strategy(self, strategy: Strategy):
        self._strategy = strategy
    
    def do_some_business_logic(self):
        result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"])
        print(",".join(result))

####客户端代码

context = Context(ConcreteStrategyA())
context.do_some_business_logic()

context.strategy = ConcreteStrategyB()
context.do_some_business_logic()

策略模式UML类图

          +-------------------+
          |    Strategy       |
          +-------------------+
          | +execute(): void  |
          +-------------------+
                  ^
                  | 实现
     +------------+-------------+
     |                          |
+-----------+             +-----------+
| ConcreteStrategyA |             | ConcreteStrategyB |
+-----------+             +-----------+
| +execute()       |             | +execute()       |
+-----------+             +-----------+

          +-------------------+
          |     Context       |
          +-------------------+
          | -strategy: Strategy |
          | +setStrategy(Strategy) |
          | +executeStrategy()  |
          +-------------------+

trategy(策略接口):定义了一个公共接口,各种不同的算法以不同的方式实现这个接口。Context使用这个接口调用具体策略定义的算法。 ConcreteStrategy(具体策略):Strategy接口的具体实现,每个实现代表了一个算法。 Context(环境类):持有一个策略类的引用,最终给客户端调用。它不决定哪一个算法是正确的,完全由客户端决定。

具体应用和使用场景

支付方式选择

在电商平台或在线支付系统中,用户可以选择多种支付方式(如支付宝、微信支付、银行卡等)。策略模式可以将每种支付方式实现为一个具体的策略类,支付系统的上下文(Context)根据用户的选择动态切换支付策略。

from abc import ABC, abstractmethod

# 策略接口
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

# 具体策略类:支付宝支付
class AlipayPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"支付宝支付{amount}元")

# 具体策略类:微信支付
class WechatPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"微信支付{amount}元")

# 环境类
class PaymentContext:
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def execute_payment(self, amount):
        self._strategy.pay(amount)

# 客户端代码
if __name__ == "__main__":
    context = PaymentContext(AlipayPayment())
    context.execute_payment(100)

    context = PaymentContext(WechatPayment())
    context.execute_payment(50)

数据压缩工具

一个支持多种压缩算法(如ZIP、RAR、7z等)的工具可以使用策略模式来设计。每种压缩算法都作为一个具体策略实现,用户可以根据需要选择不同的压缩策略。

from abc import ABC, abstractmethod
import zipfile
import rarfile

# 策略接口
class CompressionStrategy(ABC):
    @abstractmethod
    def compress(self, files, archive):
        pass

# 具体策略类:ZIP压缩
class ZipCompressionStrategy(CompressionStrategy):
    def compress(self, files, archive):
        with zipfile.ZipFile(archive, 'w') as zipf:
            for file in files:
                zipf.write(file)
        print(f"{archive} has been created with ZIP compression.")

# 具体策略类:RAR压缩
class RarCompressionStrategy(CompressionStrategy):
    def compress(self, files, archive):
        with rarfile.RarFile(archive, 'w') as rarf:
            for file in files:
                rarf.write(file)
        print(f"{archive} has been created with RAR compression.")

# 环境类
class CompressionContext:
    def __init__(self, strategy: CompressionStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: CompressionStrategy):
        self._strategy = strategy

    def create_archive(self, files, archive):
        self._strategy.compress(files, archive)

# 客户端代码
if __name__ == "__main__":
    files = ['example.txt', 'example2.txt']
    context = CompressionContext(ZipCompressionStrategy())
    context.create_archive(files, 'archive.zip')

    context.set_strategy(RarCompressionStrategy())
    context.create_archive(files, 'archive.rar')

表单验证

在Web开发中,表单验证是一个常见需求。不同的字段可能需要不同的验证策略(如邮箱验证、手机号验证、密码强度验证等)。通过策略模式,可以为每种验证需求实现一个具体策略类,从而使验证逻辑更加灵活和可扩展。

from abc import ABC, abstractmethod
import re

# 验证策略接口
class ValidationStrategy(ABC):
    @abstractmethod
    def validate(self, value):
        pass

# 具体策略类:邮箱验证
class EmailValidationStrategy(ValidationStrategy):
    def validate(self, value):
        if re.match(r"[^@]+@[^@]+\.[^@]+", value):
            print("邮箱格式正确。")
            return True
        else:
            print("邮箱格式错误。")
            return False

# 具体策略类:手机号验证
class PhoneValidationStrategy(ValidationStrategy):
    def validate(self, value):
        if re.match(r"^1[3-9]\d{9}$", value):
            print("手机号格式正确。")
            return True
        else:
            print("手机号格式错误。")
            return False

# 环境类
class ValidationContext:
    def __init__(self, strategy: ValidationStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: ValidationStrategy):
        self._strategy = strategy

    def validate_field(self, value):
        return self._strategy.validate(value)

# 客户端代码
if __name__ == "__main__":
    email_validator = ValidationContext(EmailValidationStrategy())
    phone_validator = ValidationContext(PhoneValidationStrategy())

    email_validator.validate_field("[email protected]")
    phone_validator.validate_field("13800000000")

路由算法

在网络设备或软件中,根据不同的网络条件选择最优的路由算法是很常见的需求。例如,可以基于最短路径、最少跳数或最低成本等标准来选择路由。每一种路由算法都可以实现为一个具体的策略类。

from abc import ABC, abstractmethod

# 路由策略接口
class RoutingStrategy(ABC):
    @abstractmethod
    def find_route(self, source, destination):
        pass

# 具体策略类:最短路径优先
class ShortestPathStrategy(RoutingStrategy):
    def find_route(self, source, destination):
        # 假设的算法实现,实际应用中需要替换为具体的最短路径算法
        print(f"从{source}到{destination}的最短路径已选择。")

# 具体策略类:最少跳数优先
class LeastHopsStrategy(RoutingStrategy):
    def find_route(self, source, destination):
        # 假设的算法实现,实际应用中需要替换为具体的最少跳数算法
        print(f"从{source}到{destination}的最少跳数路径已选择。")

# 环境类
class Router:
    def __init__(self, strategy: RoutingStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: RoutingStrategy):
        self._strategy = strategy

    def route(self, source, destination):
        self._strategy.find_route(source, destination)

# 客户端代码
if __name__ == "__main__":
    router = Router(ShortestPathStrategy())
    router.route("A", "B")

    router.set_strategy(LeastHopsStrategy())
    router.route("A", "C")

日志记录

在软件系统中,可能需要将日志记录到不同的地方,比如控制台、文件或远程服务器等。通过策略模式,可以为每种日志记录方式实现一个具体策略类,使得日志记录方式可以灵活切换。

from abc import ABC, abstractmethod
import datetime

# 日志记录策略接口
class LogStrategy(ABC):
    @abstractmethod
    def log(self, message):
        pass

# 具体策略类:控制台日志记录
class ConsoleLogStrategy(LogStrategy):
    def log(self, message):
        print(f"{datetime.datetime.now()}: {message}")

# 具体策略类:文件日志记录
class FileLogStrategy(LogStrategy):
    def __init__(self, file_name):
        self.file_name = file_name

    def log(self, message):
        with open(self.file_name, 'a') as file:
            file.write(f"{datetime.datetime.now()}: {message}\n")

# 环境类
class Logger:
    def __init__(self, strategy: LogStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: LogStrategy):
        self._strategy = strategy

    def log(self, message):
        self._strategy.log(message)

# 客户端代码
if __name__ == "__main__":
    logger = Logger(ConsoleLogStrategy())
    logger.log("这是一条控制台日志信息。")

    logger.set_strategy(FileLogStrategy("logs.txt"))
    logger.log("这是一条文件日志信息。")

折扣计算

在销售系统中,根据不同的促销活动(如满减、打折、返现等)计算折扣。每种促销活动都可以实现为一个具体策略类,销售系统根据当前的促销策略来计算最终价格。

from abc import ABC, abstractmethod

# 折扣策略接口
class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, amount):
        pass

# 具体策略类:固定金额折扣
class FixedDiscountStrategy(DiscountStrategy):
    def __init__(self, discount):
        self.discount = discount

    def apply_discount(self, amount):
        return max(amount - self.discount, 0)

# 具体策略类:百分比折扣
class PercentageDiscountStrategy(DiscountStrategy):
    def __init__(self, percentage):
        self.percentage = percentage

    def apply_discount(self, amount):
        return amount * (1 - self.percentage / 100.0)

# 环境类
class DiscountContext:
    def __init__(self, strategy: DiscountStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: DiscountStrategy):
        self._strategy = strategy

    def calculate_price(self, amount):
        return self._strategy.apply_discount(amount)

if __name__ == "__main__":
    # 创建一个DiscountContext对象,初始策略为固定金额折扣10
    discount_context = DiscountContext(FixedDiscountStrategy(10))
    # 计算并打印固定金额折扣后的价格
    print(f"固定金额折扣后价格:{discount_context.calculate_price(100)}")  # 输出应该是90

    # 更改策略为百分比折扣20%
    discount_context.set_strategy(PercentageDiscountStrategy(20))
    # 计算并打印百分比折扣后的价格
    print(f"百分比折扣后价格:{discount_context.calculate_price(100)}")  # 输出应该是80

策略模式和简单工厂模式的不同点

策略模式(Strategy Pattern)和简单工厂模式(Simple Factory Pattern)是两种常用的设计模式,它们在解决问题的方式和应用场景上有所不同。

以下是它们之间的主要区别:

  • 策略模式
    • 目的:策略模式的目的是定义一系列算法,将它们封装起来,并使它们可以互相替换。策略模式允许算法在不影响客户端的情况下独立于使用它们的客户端变化。
    • 应用场景:当有多种类似的操作或算法,且它们之间仅在实现细节上有所不同时,可以使用策略模式。它允许在运行时选择最适合的算法或操作。
    • 实现方式:通常通过定义一个公共接口来实现,各个策略类实现这个接口,客户端通过持有一个对这个接口的引用来使用不同的策略。
  • 简单工厂模式
    • 目的:简单工厂模式的目的是创建对象。它提供一个创建对象的接口,将对象的创建过程封装起来,客户端不需要知道具体的类名,只需要知道相应的参数。
    • 应用场景:当创建对象的逻辑比较复杂,但是又希望客户端代码与对象创建过程解耦时,可以使用简单工厂模式。它常用于创建相似对象的场景。
    • 实现方式:通过一个工厂类来实现,工厂类有一个静态方法,根据不同的参数返回不同类的实例。客户端只需要调用这个方法并传递相应的参数,无需直接实例化对象。
  • 主要区别
    • 目标不同:策略模式主要用于封装算法族和动态选择算法,而简单工厂模式主要用于创建对象。
    • 应用场景不同:策略模式适用于算法或操作多样性的场景,简单工厂模式适用于创建对象时需要隐藏创建逻辑的场景。
    • 实现方式和侧重点不同:策略模式侧重于定义一系列可互换的算法,并让客户端可以动态选择使用哪一种算法。简单工厂模式侧重于封装对象创建过程,减少客户端与具体类之间的依赖。

简而言之,策略模式关注于算法和行为的多样性及其可替换性,而简单工厂模式关注于创建具体对象,隐藏创建细节,减少客户端与具体类之间的依赖。