Python如何用元类替代工厂模式实现不同类型CarService自动实例化
实现方案
你要的「调用基类CarService构造器自动返回对应车型子类实例」的需求,不需要用到复杂的元类特性,重写基类的__new__方法就能实现,是更轻量的方案。如果一定要用元类实现,我们也会给出对应方案。
方案1:重写基类__new__方法(推荐)
Python实例化对象的流程是:先调用类的__new__方法创建实例对象,再调用__init__对实例做初始化。我们可以在基类CarService的__new__中完成车型查询、子类匹配逻辑,直接返回对应子类的实例。
完整实现代码如下:
from abc import ABC, abstractmethod from enum import Enum from dataclasses import dataclass import uuid from typing import Dict, Type # 基础定义 class CarTypes(Enum): SUV = 'suv' SPORT = 'sport' @dataclass class Car: id: uuid.UUID type: CarTypes owner: str def run(self): pass # 模拟CarRepository class CarRepository: _mock_cars = { uuid.UUID('00000000-0000-0000-0000-000000000001'): Car( id=uuid.UUID('00000000-0000-0000-0000-000000000001'), type=CarTypes.SUV, owner='张三' ), uuid.UUID('00000000-0000-0000-0000-000000000002'): Car( id=uuid.UUID('00000000-0000-0000-0000-000000000002'), type=CarTypes.SPORT, owner='李四' ) } @classmethod def get(cls, car_id: uuid.UUID) -> Car: return cls._mock_cars[car_id] # 服务类定义 class CarService(ABC): # 注册车型和对应服务类的映射,新增车型只需要加映射和对应服务子类即可,符合开闭原则 _service_map: Dict[CarTypes, Type['CarService']] = {} def __new__(cls, car_id: uuid.UUID): # 如果当前调用的是子类,直接返回子类实例 if cls != CarService: return super().__new__(cls) # 基类调用时,查询车辆信息匹配对应子类 car = CarRepository.get(car_id) service_cls = cls._service_map[car.type] # 调用子类构造器返回实例 return service_cls(car) def __init__(self, car: Car): self._car = car @abstractmethod def run(self) -> None: pass # 子类实现 class SUVCarService(CarService): def run(self): print(f"SUV车型 {self._car.id} 执行专属逻辑,调用SUV专属API") class SportCarService(CarService): def run(self): print(f"跑车车型 {self._car.id} 执行专属逻辑,调用跑车专属API") # 注册车型和服务类的映射 CarService._service_map[CarTypes.SUV] = SUVCarService CarService._service_map[CarTypes.SPORT] = SportCarService
使用效果完全符合你的要求:
def controller_run(body): service = CarService(car_id=body['car_id']) print(type(service)) service.run() # 测试 controller_run({'car_id': uuid.UUID('00000000-0000-0000-0000-000000000001')}) # 输出:<class '__main__.SUVCarService'> # 输出:SUV车型 00000000-0000-0000-0000-000000000001 执行专属逻辑,调用SUV专属API controller_run({'car_id': uuid.UUID('00000000-0000-0000-0000-000000000002')}) # 输出:<class '__main__.SportCarService'> # 输出:跑车车型 00000000-0000-0000-0000-000000000002 执行专属逻辑,调用跑车专属API
方案2:元类实现
如果你一定要用元类实现,核心是重写元类的__call__方法:因为类本身是元类的实例,调用类(比如CarService())的本质是调用元类的__call__方法,我们可以在这个方法里控制实例的返回逻辑。
注意__prepare__是元类用来初始化类命名空间的,__new__是元类用来创建类本身的,都不适合用来控制类实例化的过程,只有__call__是对应类调用的逻辑
元类版本的实现代码如下:
class CarServiceMeta(type): def __call__(cls, car_id: uuid.UUID): # 仅基类调用时走匹配逻辑 if cls.__name__ != 'CarService': return super().__call__(car_id) car = CarRepository.get(car_id) service_cls = cls._service_map[car.type] return service_cls(car) # 基类指定元类 class CarService(ABC, metaclass=CarServiceMeta): _service_map: Dict[CarTypes, Type['CarService']] = {} def __init__(self, car: Car): self._car = car @abstractmethod def run(self) -> None: pass # 子类和注册逻辑和方案1完全一致 class SUVCarService(CarService): def run(self): print(f"SUV车型 {self._car.id} 执行专属逻辑,调用SUV专属API") class SportCarService(CarService): def run(self): print(f"跑车车型 {self._car.id} 执行专属逻辑,调用跑车专属API") CarService._service_map[CarTypes.SUV] = SUVCarService CarService._service_map[CarTypes.SPORT] = SportCarService
使用效果和方案1完全一致。
优化点
你还可以通过装饰器自动注册子类,不需要手动维护_service_map,新增车型只需要加@register_service(CarTypes.XXX)装饰器即可,完全符合开闭原则:
def register_service(car_type: CarTypes): def wrapper(cls): CarService._service_map[car_type] = cls return cls return wrapper @register_service(CarTypes.SUV) class SUVCarService(CarService): def run(self): ... @register_service(CarTypes.SPORT) class SportCarService(CarService): def run(self): ...
内容的提问来源于stack exchange,提问作者Tâmer Cuba
相关产品推荐
相关产品推荐

