设计模式 -- 适配器 & 策略模式

问题:API接口不统一

问题场景

假设你要发送邮件,但不同服务商调用方式不同。比如发送邮件,SendGrid、Mailgun、Microsoft Graph 都能发,但调用方式天差地别

SendGrid 的调用方式:

ini 复制代码
# SendGrid SDK的调用方式
sg = sendgrid.SendGridAPIClient(api_key='SG.xxx')
message = Mail(
    from_email='from@example.com',
    to_emails='to@example.com',
    subject='Hello',
    plain_text_content='World'
)
response = sg.send(message)

Mailgun 的调用方式:

perl 复制代码
# Mailgun SDK的调用方式
requests.post(
    "https://api.mailgun.net/v3/your-domain/messages",
    auth=("api", "your-api-key"),
    data={
        "from": "from@example.com",
        "to": "to@example.com",
        "subject": "Hello",
        "text": "World"
    }
)

问题:看到区别了吗?参数名不一样(to_emails vs to),调用方式不一样(对象方法 vs HTTP请求),返回格式也不一样。

如果直接在业务代码中调用这些SDK:

python 复制代码
# 订单服务中
def send_order_confirmation(order_id):
    sg = sendgrid.SendGridAPIClient(api_key='SG.xxx')
    message = Mail(...)
    sg.send(message)

# 用户服务中
def send_welcome_email(user_email):
    sg = sendgrid.SendGridAPIClient(api_key='SG.xxx')
    message = Mail(...)
    sg.send(message)

# ... 还有20个地方都在调用SendGrid

如果要换成Mailgun,得把所有地方都改一遍。

当然,我们可以封装下

python 复制代码
# 封装成一个函数
def send_email(to, subject, content, provider='sendgrid'):
    if provider == 'sendgrid':
        sg = sendgrid.SendGridAPIClient(api_key='SG.xxx')
        message = Mail(from_email=..., to_emails=to, ...)
        sg.send(message)
    elif provider == 'mailgun':
        requests.post("https://api.mailgun.net/v3/...", 
                     auth=("api", "..."), 
                     data={"from": ..., "to": to, ...})
    elif provider == 'microsoft_graph':
        # 又是另一套代码
        ...

# 业务代码就可以这样用了
send_email("user@example.com", "Hello", "World", provider='sendgrid')

封装后确实好多了,业务代码清爽了。但是这种封装方式依然有问题。

函数越来越长 :新增服务商要在这个函数中继续加一个分支,代码越来越长。找个某个特定的服务商要在这个很长的函数中找,容易改错。总而言之,就是不好维护。

当然,可以进一步优化,把不同服务商封装到不同的类中。

python 复制代码
class SendGridEmail:
    def send(self, to, subject, content):
        # SendGrid 的代码
        ...

class MailgunEmail:
    def send(self, to, subject, content):
        # Mailgun 的代码
        ...

好多了,但是,这里有个关键问题接口不统一

看回最开始的问题:

  • SendGrid 用 to_emails,Mailgun 用 to
  • SendGrid 用 plain_text_content,Mailgun 用 text

如果只是简单封装到类里,接口还是不统一

python 复制代码
# 每个服务商的接口不一样,业务代码还是要判断,而且调用方式还不一样!
if provider == 'sendgrid':
    email = SendGridEmail()
    email.send(to_emails=to, subject=subject, plain_text_content=content)  # 参数名不一样
elif provider == 'mailgun':
    email = MailgunEmail()
    email.send(to=to, subject=subject, text=content)  # 参数名不一样

适配器模式的作用**:统一接口 我们想要业务代码只关心"发邮件"这个功能,不关心用哪个服务商。

python 复制代码
# 业务代码只需要这样写,不管用哪个服务商
manager.send_email(
    to="user@example.com",
    subject="Hello",
    content="World"
)

# 切换服务商?改配置文件即可,无需修改发邮件代码

适配器模式

什么是适配器模式?

核心思想让所有服务商的类实现同一个接口

简单说 :适配器模式就是定义一个统一的接口(抽象类),让不同的实现类都遵循这个接口,这样业务代码就可以统一调用,不需要关心具体是哪个实现。

生活中的例子:中国手机插头插不进欧洲插座,买个转换插头就行了。转换插头做了什么?

  • 不改变你的手机
  • 不改变欧洲的插座
  • 只是在中间做了个"翻译",统一了接口

抽象类 (Abstract Class)就是定义了接口但自己不实现具体功能的类。它就像一个"模板",告诉子类"你必须实现这些方法"。

在 Python 中,使用 ABC(Abstract Base Class)和 @abstractmethod 来定义抽象类:

python 复制代码
from abc import ABC, abstractmethod

class BaseEmailAdapter(ABC):  # ABC 表示这是一个抽象基类
    @abstractmethod  # 这个装饰器表示子类必须实现这个方法
    def send(self, to, subject, content):
        pass  # 抽象方法不写具体实现,只是定义接口

适配器模式的实现

python 复制代码
# 1. 先定义统一接口(抽象类)
from abc import ABC, abstractmethod

class BaseEmailAdapter(ABC):  # 抽象基类
    @abstractmethod
    def send(self, to, subject, content):  # 统一的参数
        """子类必须实现这个方法"""
        pass

# 2. 每个服务商实现这个接口(继承抽象类)
class SendGridAdapter(BaseEmailAdapter):
    def send(self, to, subject, content):  # 必须和接口一样
        # SendGrid 的代码,内部转换成 SendGrid 的调用方式
        sg = sendgrid.SendGridAPIClient(api_key=self.api_key)
        message = Mail(
            from_email=self.from_email,
            to_emails=to,  # 统一接口用 to,内部转换成 SendGrid 的 to_emails
            subject=subject,
            plain_text_content=content  # 统一接口用 content,内部转换成 plain_text_content
        )
        return sg.send(message)

class MailgunAdapter(BaseEmailAdapter):
    def send(self, to, subject, content):  # 必须和接口一样
        # Mailgun 的代码,内部转换成 Mailgun 的调用方式
        requests.post(
            "https://api.mailgun.net/v3/your-domain/messages",
            auth=("api", self.api_key),
            data={
                "from": self.from_email,
                "to": to,  # 统一接口用 to,Mailgun 也用 to,直接传
                "subject": subject,
                "text": content  # 统一接口用 content,内部转换成 Mailgun 的 text
            }
        )



def create_adapter(provider):
    if provider == 'sendgrid':
        return SendGridAdapter(...)
    elif provider == 'mailgun':
        return MailgunAdapter(...)
# 3. 业务代码只需要依赖接口,不需要知道具体是哪个服务商
adapter = create_adapter(provider)  # 根据配置创建适配器(这里只有一次 if-else)
adapter.send(to, subject, content)  # 统一调用,参数都一样!

策略模式:优化选择逻辑

核心思想

策略模式:封装选择逻辑,根据情况选择使用哪个策略

两种实现方式

方式1:if-else(简单实现) 根据 provider 选择不同的适配器。

python 复制代码
def create_adapter(provider):
    if provider == 'sendgrid':
        return SendGridAdapter(...)
    elif provider == 'mailgun':
        return MailgunAdapter(...)
    # 添加新服务商?又要加一个 elif

问题 :每次添加新服务商,都要改这个函数,加新的 elif

方式2:字典映射(优雅实现)

python 复制代码
class EmailManager:
    def __init__(self):
        # 策略映射:把服务商名称映射到对应的适配器类
        self._adapter_classes = {
            'sendgrid': SendGridAdapter,
            'mailgun': MailgunAdapter,
            'microsoft_graph': MicrosoftGraphAdapter
        }
    
    def _create_adapter(self, provider):
        # 从字典中获取对应的适配器类
        adapter_class = self._adapter_classes.get(provider)
        if not adapter_class:
            raise ValueError(f"不支持的邮件服务商: {provider}")
        return adapter_class(api_key=..., from_email=...)
    
    def send_email(self, to, subject, content):
        # 1. 从配置获取服务商
        provider = self._get_provider_from_config()
        
        # 2. 创建适配器(策略模式:根据配置选择)
        adapter = self._create_adapter(provider)
        
        # 3. 调用适配器的 send 方法(适配器模式:统一接口)
        return adapter.send(to, subject, content)

两种模式的协作

完整流程

scss 复制代码
业务代码
  ↓
EmailManager.send_email()  (策略模式:选择适配器)
  ↓
_create_adapter(provider)  (策略模式:根据配置选择)
  ↓
SendGridAdapter / MailgunAdapter  (适配器模式:统一接口)
  ↓
adapter.send()            (适配器模式:统一调用)
  ↓
SendGrid API / Mailgun API (第三方API)

总结

适配器模式

解决的问题 :接口不统一
解决方案 :定义统一接口(抽象类),所有适配器都实现相同的接口
价值:业务代码可以统一调用,不需要关心具体实现

策略模式

核心:封装选择逻辑,根据情况选择使用哪个策略

反思

人生怎么过都可以。

你想要怎样的人生?

相关推荐
2401_841495641 天前
【机器学习】深度信念网络(DBN)
人工智能·python·深度学习·神经网络·机器学习·无监督预训练·有监督微调
0和1的舞者1 天前
字典与文件操作全解析
python·学习
Dxy12393102161 天前
报错:OSError: [WinError 1455] 页面文件太小,无法完成操作
python
好汉学技术1 天前
Scrapy框架数据存储完全指南(实操为主,易落地)
python
想看一次满天星1 天前
某里231——AST解混淆流程
爬虫·python·ast·js·解混淆
没有钱的钱仔1 天前
python 记录
开发语言·python
vibag1 天前
RAG文本处理
python·语言模型·langchain·大模型
nvd111 天前
疑难杂症:如何让 Cline 乖乖使用 MCP Tool?
python
vibag1 天前
MCP实践
python·语言模型·langchain·大模型