用于注册数据提供者以处理多类型对象的设计模式选型
嘿,你的思路已经非常接近了!咱们来把这个方案用合适的设计模式打磨得更健壮、更灵活,完美匹配你需要的动态注册、类型匹配、可扩展需求。
基于策略模式+服务定位器的多数据源数据提供者方案
你的场景核心是根据对象类型匹配对应的数据获取策略,同时要支持动态扩展——这刚好是**策略模式(Strategy Pattern)搭配服务定位器(Registry/Service Locator)**的典型应用场景,咱们一步步完善你的代码:
1. 先规范核心接口
首先要统一数据提供者和目标对象的接口,避免后续扩展时出现类型混乱:
// 定义所有数据提供者必须实现的接口 interface DataProviderInterface { // 返回该提供者支持处理的对象类型(用类名比字符串更安全) public function supportsType(): string; // 根据目标对象获取对应数据(允许传入对象,方便后续需要对象参数的场景) public function fetchData(object $target): array; } // 可选:定义目标对象的通用接口,强制规范类型标识逻辑 interface DataTargetInterface { public function getDataType(): string; }
2. 实现具体的提供者与目标对象
以你的城市、天气、人口数据为例:
// 天气数据提供者(对接第三方服务) class WeatherDataProvider implements DataProviderInterface { public function supportsType(): string { return Weather::class; } public function fetchData(object $target): array { // 先做类型校验,避免传错对象 if (!$target instanceof Weather) { throw new InvalidArgumentException("This provider only supports Weather objects"); } // 调用第三方API获取对应城市的天气 return [ 'temperature' => 28, 'humidity' => 55, 'condition' => 'Partly Cloudy' ]; } } // 天气目标对象 class Weather implements DataTargetInterface { private int $cityId; public function __construct(int $cityId) { $this->cityId = $cityId; } public function getDataType(): string { return self::class; } public function getCityId(): int { return $this->cityId; } } // 城市基础信息提供者(对接MySQL) class CityMetadataProvider implements DataProviderInterface { public function supportsType(): string { return City::class; } public function fetchData(object $target): array { if (!$target instanceof City) { throw new InvalidArgumentException("This provider only supports City objects"); } // 从MySQL city表查询数据 return [ 'id' => $target->getId(), 'name' => 'Shanghai', 'province' => 'Shanghai', 'country' => 'China' ]; } } // 城市目标对象 class City implements DataTargetInterface { private int $id; public function __construct(int $id) { $this->id = $id; } public function getDataType(): string { return self::class; } public function getId(): int { return $this->id; } }
3. 完善DataProviderRegistry(服务定位器核心)
你的注册表已经是核心逻辑,现在优化它的健壮性和易用性:
class DataProviderRegistry { /** @var DataProviderInterface[] 存储类型与提供者的映射 */ private array $providers = []; // 注册单个数据提供者 public function register(DataProviderInterface $provider): void { $type = $provider->supportsType(); // 可以选择覆盖已有提供者,或者抛出异常防止重复注册,根据业务需求调整 $this->providers[$type] = $provider; } // 批量注册提供者(简化多提供者注册流程) public function registerMultiple(DataProviderInterface ...$providers): void { foreach ($providers as $provider) { $this->register($provider); } } // 根据目标对象获取对应数据 public function getData(object $target): array { // 优先使用对象自身的类型标识,兼容未实现接口的对象(用类名作为 fallback) $type = $target instanceof DataTargetInterface ? $target->getDataType() : get_class($target); if (!isset($this->providers[$type])) { throw new RuntimeException("No data provider found for type: {$type}"); } return $this->providers[$type]->fetchData($target); } }
4. 实际使用示例
现在就可以像你期望的那样灵活使用了:
// 创建需要获取数据的对象 $city = new City(101); $weather = new Weather(101); // 后续新增人口统计对象 $demographic = new Demographic(101); // 初始化注册表并注册所有提供者 $dataProviderRegistry = new DataProviderRegistry(); $dataProviderRegistry->registerMultiple( new CityMetadataProvider(), new WeatherDataProvider(), new DemographicDataProvider() // 新增提供者直接注册即可 ); // 批量获取数据并处理(比如渲染表格) $objects = [$city, $weather, $demographic]; foreach ($objects as $object) { $data = $dataProviderRegistry->getData($object); // 这里可以把数据组装成表格行 var_dump($data); }
为什么这个方案适合你?
- 策略模式:每个数据提供者都是独立的策略,负责自己的数据源逻辑(MySQL/第三方API/MongoDB),完全解耦,新增提供者只需要实现
DataProviderInterface,不需要修改注册表代码。 - 服务定位器:集中管理所有提供者,提供统一的数据获取入口,避免在业务代码中到处实例化提供者。
- 开闭原则:后续新增任何对象类型(比如城市交通数据),只需要三步:
- 新建目标对象类(可选实现
DataTargetInterface) - 新建对应的
DataProviderInterface实现类 - 注册到注册表中即可,完全不需要修改现有代码。
- 新建目标对象类(可选实现
额外优化建议
- 缓存机制:如果数据获取成本高(比如第三方API),可以在注册表或提供者中加入缓存逻辑,避免重复请求。
- 异步支持:如果某些数据源是异步的,可以把
fetchData改成异步方法,配合协程使用。 - 多提供者优先级:如果同一个类型有多个备用数据源,可以在注册表中支持优先级排序,获取时选择最高优先级的提供者。
内容的提问来源于stack exchange,提问作者vishal
相关产品推荐
相关产品推荐

