Skip to content

使用范型缩小子类参数类型

约 1147 字大约 4 分钟

Python

2024-10-22

在面向对象编程中,基类通常定义了一般性的行为,而子类则可以对这些行为进行扩展或细化。利用范型(Generic) ,我们可以编写更加灵活、可重用的代码。在这种情况下,子类不仅可以继承基类的方法,还可以对方法的参数类型进行进一步限制或缩小,从而提高代码的安全性和可读性。

本文将通过一个具体的代码示例,展示如何在Python中使用范型,并通过子类缩小父类方法参数的类型。

示例

范型是一种设计模式,可以使类或函数的类型参数化。通过引入类型参数,我们可以在不同的上下文中复用相同的代码,同时保持类型安全。typing.Generic​和TypeVar​是Python中实现范型的核心。

让我们看一个范型和子类缩小类型的具体例子:

from typing import Generic, TypeVar
from pydantic import BaseModel
import abc

class BaseParams(BaseModel):
    action: str

class CreateParams(BaseParams):
    name: str
    node_ids: list[int]

class ExpandParams(BaseParams):
    cluster_id: int
    node_ids: list[int]

TParams = TypeVar("TParams", bound=BaseParams)

class BaseAction(Generic[TParams]):
    def __init__(self, params: TParams):
        self.params = params
        self.action = params.action

    @abc.abstractmethod
    def run(self):
        raise NotImplementedError

class CreateAction(BaseAction[CreateParams]):
    def __init__(self, params: CreateParams):
        super().__init__(params)
        self.name: str = params.name
        self.node_ids: list[int] = params.node_ids
        self.action: str = params.action

    def run(self) -> None:
        print(
            "{} cluster {}, use nodes: {}".format(self.action, self.name, self.node_ids)
        )
        return None

class ExpandAction(BaseAction[ExpandParams]):
    def __init__(self, params: ExpandParams):
        super().__init__(params)
        self.cluster_id: int = params.cluster_id
        self.node_ids: list[int] = params.node_ids
        self.action: str = params.action

    def run(self) -> None:
        print(
            "{} cluster {}, use nodes: {}".format(
                self.action, self.cluster_id, self.node_ids
            )
        )
        return None

在上面的代码中,我们定义了一个基类 BaseAction​,它使用了一个范型 TParams​,其中 TParams​ 被限定为 BaseParams​ 或其子类。这意味着任何继承 BaseAction​ 的子类都必须使用 BaseParams​ 的子类作为参数类型。

解析

  1. 定义基础参数类:BaseParams​ 是所有参数的基类,包含了一个通用的 action​ 字段。它的两个子类 CreateParams​ 和 ExpandParams​ 分别添加了更多特定的字段,如 name​、node_ids​ 和 cluster_id​。
  2. 范型与基类的结合:BaseAction​ 类被定义为一个范型类,接受一个类型参数 TParams​,并在构造函数中将 params​ 的类型设为 TParams​。这使得 BaseAction​ 具有了更大的灵活性,它可以处理不同类型的参数。
  3. 子类缩小参数类型: 我们创建了两个子类 CreateAction​ 和 ExpandAction​,它们分别继承自 BaseAction​,并且将 TParams​ 限制为 CreateParams​ 和 ExpandParams​。在各自的 run​ 方法中,子类进一步细化了参数,如 CreateAction​ 需要处理 name​,而 ExpandAction​ 则处理 cluster_id​。

优势

  1. 提高代码的安全性: 通过子类缩小参数类型,我们可以确保在每个子类中,方法 run​ 只能接受与该子类对应的参数类型。这种类型检查机制有助于在编译时发现潜在的错误。
  2. 增强可读性和可维护性: 子类明确了各自的参数要求,不同的业务逻辑由各自的子类独立管理。这样,代码更容易理解和维护。
  3. 重用代码:BaseAction​ 实现了范型,允许子类在保持通用行为(如构造函数)的同时,定义各自特定的逻辑(如 run​ 方法)。这避免了重复代码,同时又能根据需要定制行为。

结果

当我们运行上述代码时:

if __name__ == "__main__":
    create_params = CreateParams(action="create", name="test", node_ids=[1, 2, 3])
    create_action = CreateAction(create_params)
    create_action.run()

    expand_params = ExpandParams(action="expand", cluster_id=1, node_ids=[1, 2, 4])
    expand_action = ExpandAction(expand_params)
    expand_action.run()

输出将如下所示:

create cluster test, use nodes: [1, 2, 3]
expand cluster 1, use nodes: [1, 2, 4]

可以看到,CreateAction​ 和 ExpandAction​ 使用了各自特定的参数,并且 run​ 方法按照各自的逻辑生成了对应的输出。

总结

通过结合范型与继承,Python 提供了一种优雅的方式来缩小子类方法的参数类型。这种设计模式不仅提高了代码的类型安全性,还能增强代码的可读性和重用性。对于需要处理不同参数类型的复杂系统来说,使用范型可以帮助我们编写更加灵活、健壮的代码。

这种模式在现实应用中非常常见,尤其是在开发涉及多个业务场景的应用时,每个场景可能都有自己的特定参数和处理逻辑,但它们共享相似的操作。通过范型和子类,开发者可以简化代码结构,提高开发效率。