
本文探讨了如何在PHP中遵循协变/逆变原则的同时,避免编写重复代码。通过移除`BaseBarClass::getFooBase()`方法的返回类型声明,或者在PHP8及以上版本中使用`: mixed`,可以解决子类`getFoo()`方法违反协变/逆变原则的问题,同时保持代码的简洁性和可维护性。
在面向对象编程中,协变和逆变是类型系统中的重要概念,它们描述了方法参数和返回值类型在继承关系中的变化规则。在PHP中,严格遵循这些规则可以提高代码的健壮性和可维护性。然而,在某些情况下,为了避免代码重复,我们可能会遇到违反这些规则的情况。本文将探讨一种解决此类问题的实用方法。
考虑以下场景:我们有一个基类BaseFooClass和多个子类ChildFooClass1、ChildFooClass2等。还有一个基类BaseBarClass,它负责创建BaseFooClass及其子类的实例。BaseBarClass有一个受保护的方法getFooBase(),用于从远程数据源获取数据并创建BaseFooClass的实例。每个ChildBarClass都有一个getFoo()方法,该方法调用getFooBase()来获取特定类型的ChildFooClass实例。
以下是代码示例:
keys as $key => $value) { $this->map[$key] = $keyValuePairs[$key] ?? null; } }}class ChildFooClass1 extends BaseFooClass { protected $keys = ['foo1_a', 'foo1_b'];}class ChildFooClass2 extends BaseFooClass { protected $keys = ['foo2_a', 'foo2_b', 'foo2_c'];}abstract class BaseBarClass { protected $classIndex; protected function getFooBase(int $dataIndex) //: ?BaseFooClass Remove the return type or use : mixed { // GetRemoteData is assumed to be a global function, the important thing here is the retrieved data depends on classIndex and dataIndex // If $classIndex is 1, the $keyValuePairs will look like ['foo1_a' => value1, 'foo1_b' => value2] where value1 and value2 depend on $dataIndex $keyValuePairs = GetRemoteData($this->classIndex, $dataIndex); if (checkDataIntegrity($keyValuePairs)) { $class = "ChildFooClass" . $this->classIndex; return new $class($keyValuePairs); } return null; }}class ChildBarClass1 extends BaseBarClass { protected $classIndex=1; public function getFoo(int $dataIndex) : ?ChildFooClass1 { return $this->getFooBase($dataIndex); }}class ChildBarClass2 extends BaseBarClass { protected $classIndex=2; // input to getFoo in each BarClass can be different public function getFoo($someInput) : ?ChildFooClass2 { $dataIndex = $this->calculateDataIndex($someInput); return $this->getFooBase($dataIndex); }}// 假设的全局函数function GetRemoteData(int $classIndex, int $dataIndex): array { // 模拟从远程数据源获取数据 $data = []; if ($classIndex == 1) { $data = ['foo1_a' => 'value1_' . $dataIndex, 'foo1_b' => 'value2_' . $dataIndex]; } elseif ($classIndex == 2) { $data = ['foo2_a' => 'value3_' . $dataIndex, 'foo2_b' => 'value4_' . $dataIndex, 'foo2_c' => 'value5_' . $dataIndex]; } return $data;}function checkDataIntegrity(array $data): bool { // 模拟数据完整性检查 return !empty($data);}// 示例用法$childBar1 = new ChildBarClass1();$foo1 = $childBar1->getFoo(1);if ($foo1 instanceof ChildFooClass1) { echo "ChildBarClass1::getFoo() 返回了 ChildFooClass1 实例n";} else { echo "ChildBarClass1::getFoo() 没有返回 ChildFooClass1 实例n";}$childBar2 = new ChildBarClass2();$foo2 = $childBar2->getFoo('someInput');if ($foo2 instanceof ChildFooClass2) { echo "ChildBarClass2::getFoo() 返回了 ChildFooClass2 实例n";} else { echo "ChildBarClass2::getFoo() 没有返回 ChildFooClass2 实例n";}?>
最初的代码中,ChildBarClass1::getFoo()和ChildBarClass2::getFoo()方法违反了协变/逆变原则,因为它们试图返回更具体的类型(例如ChildFooClass1),而BaseBarClass::getFooBase()方法返回的是更通用的类型BaseFooClass。
解决方案:移除getFooBase()的返回类型声明
最简单的解决方案是从BaseBarClass::getFooBase()方法中移除返回类型声明。这样,该方法将不再强制返回特定类型,从而避免了协变/逆变冲突。由于getFooBase()是受保护的方法,不属于公共API,因此移除返回类型声明不会对代码的外部接口产生影响。
或者,在PHP8及以上版本中,可以使用: mixed作为getFooBase()的返回类型声明。mixed类型可以接受任何类型的值,因此也可以避免协变/逆变冲突。
修改后的代码如下:
abstract class BaseBarClass { protected $classIndex; protected function getFooBase(int $dataIndex) //: ?BaseFooClass Remove the return type or use : mixed { // ... }}
优点:
避免了代码重复:无需在每个ChildBarClass::getFoo()方法中重复调用GetRemoteData()和checkDataIntegrity()等逻辑。遵循协变/逆变原则:通过移除或放宽getFooBase()的返回类型声明,避免了类型冲突。保持类型安全:由于ChildBarClass::getFoo()方法仍然具有具体的返回类型声明,因此可以在编译时或运行时检测到类型错误。
注意事项:
确保GetRemoteData()函数返回的数据与ChildFooClass的构造函数兼容。如果GetRemoteData()函数返回的数据不完整或无效,checkDataIntegrity()函数应返回false,以避免创建无效的ChildFooClass实例。
总结:
通过移除或放宽基类方法的返回类型声明,可以在遵循协变/逆变原则的同时,避免编写重复代码。这种方法简单有效,可以提高代码的可维护性和可读性。在实际开发中,应根据具体情况选择合适的解决方案,并仔细考虑类型安全和代码可维护性之间的平衡。
以上就是遵循协变/逆变原则,避免重复代码的最佳实践的详细内容,更多请关注php中文网其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1329275.html
微信扫一扫
支付宝扫一扫