首页 > 解决方案 > 具有多种风格的对象的 OOP 方法

问题描述

我一直在编写代码来解析和提取机器人发送的消息中的信息。只有几种不同类型的消息,但每一种都包含我感兴趣的完全不同类型的信息,我正在努力寻找在我的代码中将它们作为对象处理的最佳方法。

如果我使用 Haskell,我只需创建一个类型Message并为每种消息定义一个定制的构造函数

data Message = Greeting Foo Bar | Warning Yadda Yadda Yadda | ...

这是一种非常好的和干净的方式,既可以将它们全部放在同一type位置,又可以轻松区分消息种类。

如何以一种 OOP 友好(或更好的 Pythonic)方式设计对象类以达到这种效果?我想到了两种方法,即:

但我对两者都不完全满意。虽然我意识到这只是一个小程序,但我想我正在利用它作为学习更多关于使用抽象和软件架构的机会。有人能给我指路吗?

标签: pythonclassobjectoopabstraction

解决方案


对于消息类设计,我会使用数据类来最小化样板。您可以完全专注于以下领域:

from dataclasses import dataclass

class Message:
    # common message methods

@dataclass
class Greeting(Message):
    foo: str
    bar: int

@dataclass
class Warning(Message):
    yadda: list[str]

一个简单的项目通常不需要更多。您可以将@classmethod工厂添加到Message基类以帮助生成特定的消息类型,并且如果在不同类型之间共享公共属性,Message也可以是工厂本身。@dataclass

也就是说,一旦您开始考虑序列化和反序列化要求,使用枚举type字段可能会有所帮助。

为了说明这一点:对于包含自动化 OpenAPI 3.1 文档的当前 RESTFul API 项目,我们使用Marshmallow处理与 JSON 之间的转换,使用marshmallow-dataclasses以避免重复定义模式和验证,以及marshmallow-oneofschema反映类型不同的类层次结构的多态模式,就像您的Message示例一样。

使用 3rd-party 库会限制您的选择,因此我使用元编程(主要是类型注释)来简洁地定义这种以枚举为键的多态类型层次结构class.__init_subclass__Generic

您的消息类型将表示如下:

class MessageType(enum.Enum):
    greeting = "greeting"
    warning = "warning"
    # ...

@dataclass
class _BaseMessage(PolymorphicType[MessageType]):
    type: MessageType
    # ...

@dataclass
class Greeting(_BaseMessage, type_key=MessageType.greeting):
    foo: str
    bar: int

@dataclass
class Warning(_BaseMessage, type_key=MessageType.warning):
    yadda: list[str]

MessageSchema = _BaseMessage.OneOfSchema("MessageSchema")

之后使用 JSON 从 JSON 加载消息,根据字典中MessageSchema.load()的键生成特定实例,例如"type"

message = MessageSchema.load({"type": "greeting", "foo": "spam", "bar": 42})
isinstance(message, Greeting)  # True

MessageSchema.dump()无论输入类型如何,都会为您提供合适的 JSON 输出:

message = Warning([42, 117])
MessageSchema.dump(message)  # {"type": "warning", "yadda": [42, 117]}

正是在enum这里的使用使集成工作得最好;PolymorphicType是处理大部分繁重工作以使_BaseMessage.OneOfSchema()调用在最后工作的自定义类。您不必使用元编程来实现最后一部分,但对我们来说,它减少了大部分marshmallow-oneschema样板文件。

此外,我们还获得了反映每种特定消息类型的 OpenAPI 模式,Redocly 等文档工具知道如何处理:

components:
  schemas:
    Message:
      oneOf:
        - $ref: '#/components/schemas/Greeting'
        - $ref: '#/components/schemas/Warning'
      discriminator:
        propertyName: type
        mapping:
          greeting: '#/components/schemas/Greeting'
          warning: '#/components/schemas/Warning'
    Greeting:
      type: object
      properties:
        type:
          type: string
          default: greeting
        foo:
          type: string
        bar:
          type: integer
    Warning:
      type: object
      properties:
        type:
          type: string
          default: warning
        yadda:
          type: array
          items:
            type: string

推荐阅读