php中的多态类方法/属性



我正在研究新的PHP 7.x"特性",但尽管它们允许伪多类类型继承,但我很失望地发现它们不能有条件地"使用"。我希望它可以允许更大的类实例多态性。

我知道有几种方法可以在几乎任何没有条件继承的半面向对象语言中模拟多态性,但我想知道是否有我在PHP中没有考虑过的方案。

我能想到的方法:

  • [有条件地]将其他类实例附加到类中的属性或属性数组。缺点:包含实例的属性/数组可能会变大,如果在多态类中设置了属性,则必须编写代码来检查和利用该属性。

  • 在类中编写多个行为,然后只有条件地执行/启用它们:缺点:类变得过于复杂,模块化程度较低,在执行之前仍然需要额外的代码来检查行为的存在/启用。

这两种方法也可能是资源密集型的(内存、CPU等(,而且很难跟踪,并且涉及与主类的丑陋的复杂交互。

用例:我正在考虑为电子商务网站创建一个产品生成器,并希望将产品的属性分解为抽象的子部分。有些零件可能会影响产品的最终价格,有些可能会增加产品的最终重量,有些可能影响产品的尺寸(用于运输(,等等。但有些零件不会起到以上任何作用。我希望能够在实例化定义产品属性(如hasPrice、addsWeight、effectsDimensions等(的子组件类时添加"行为"。我希望我可以有条件地使用Traits,但遗憾的是,事实并非如此。因此,我可能会转而将实例附加到类属性,并结合使用接口有条件地检查它们,以确保标准方法在任何附加的行为类中都可用。

我只是想知道,在我构建原型包的过程中,是否有人有其他解决方案,以至于很难回头并基于更好的想法进行重建。

好吧,这里有一种简单的方法。(使用eval-gaaahhh!(

<?php
require_once("../../vendor/autoload.php");
const TRAITS_NAMESPACE = "OurCompany\ProductBuilder\Builder\Traits";
const CLASS_NAMESPACE = "OurCompany\ProductBuilder\Builder\Components";
if(!function_exists('polymorphic_class')) {
function polymorphic_class(array $behaviors)
{
$classBody = "";
$classNameParts = [];
$behaviorUse = sprintf("use %s\ComponentAbstract;n", CLASS_NAMESPACE);
$behaviorInit = "";
$behaviorCode = "";
sort($behaviors, SORT_STRING);
foreach($behaviors as $behavior) {
switch($behavior) {
case "Price":
array_push($classNameParts, $behavior);
$behaviorUse .= sprintf("use %s\Has%s;n", TRAITS_NAMESPACE, $behavior);
$behaviorInit .= sprintf("        $this->__has%sInit();n", $behavior);
$behaviorCode .= sprintf("n    use has%s;n", $behavior);
$behaviorCode .= <<<EOT
public function setProductPrice(float $price) { return $this->setPrice(null, $price); }
public function getProductPrice() { return $this->getPrice(); }
EOT;
break;
case "Size":
array_push($classNameParts, $behavior);
$behaviorUse .= sprintf("use %s\Has%s;n", TRAITS_NAMESPACE, $behavior);
$behaviorInit .= sprintf("        $this->__has%sInit();n", $behavior);
$behaviorCode .= sprintf("n    use has%s;n", $behavior);
$behaviorCode .= <<<EOT
public function setProductWidth(float $width) { return $this->setWidth(null, $width); }
public function getProductWidth() { return $this->getWidth(); }
public function setProductLength(float $length) { return $this->setLength(null, $length); }
public function getProductLength() { return $this->getLength(); }
public function setProductHeight(float $height) { return $this->setHeight(null, $height); }
public function getProductHeight() { return $this->getHeight(); }
EOT;
break;
case "Weight":
array_push($classNameParts, $behavior);
$behaviorUse .= sprintf("use %s\Has%s;n", TRAITS_NAMESPACE, $behavior);
$behaviorInit .= sprintf("        $this->__has%sInit();n", $behavior);
$behaviorCode .= sprintf("n    use has%s;n", $behavior);
$behaviorCode .= <<<EOT
public function setProductWeight(float $weight) { return $this->setWeight(null, $weight); }
public function getProductWeight() { return $this->getWeight(); }
EOT;
break;
default:
error_log(sprintf("Unknown behavior: '%s'", $behavior));
break;
}
}
if(count($classNameParts) > 0) {
$className = "Generic" . implode("", $classNameParts);
if(!class_exists(sprintf('%s\%s', CLASS_NAMESPACE, $className))) {
$classBody = sprintf("namespace %s;nn", CLASS_NAMESPACE)
. $behaviorUse
. "nclass " . $className . " extends ComponentAbstractn{"
. $behaviorCode
. "n    public function __construct($nameOrSpec="Generic " . implode(" ", $classNameParts) . " Component", string $version=null)n"
. "    {n"
. "        parent::__construct($nameOrSpec, $version);nn"
. $behaviorInit
. "    }n}n";
//echo $classBody;
echo "creating class " . $className . PHP_EOL;
eval($classBody);
}
} else {
throw new RuntimeException(sprintf("%s - no valid behaviors specified!", __METHOD__));
}
}
}
// test all variants of generating classes on the fly
polymorphic_class(['Price']);
polymorphic_class(['Size']);
polymorphic_class(['Size']); // make sure repetition skips trying to redefine
polymorphic_class(['Weight']);
polymorphic_class(['Price', 'Size']);
polymorphic_class(['Price', 'Weight']);
polymorphic_class(['Size', 'Weight']);
polymorphic_class(['Price', 'Size', 'Weight']);
// let's see if they exist now
foreach(['Price', 'Size', 'Weight', 'PriceSize', 'PriceWeight', 'SizeWeight', 'PriceSizeWeight'] as $suff) {
if(class_exists(CLASS_NAMESPACE.'\Generic'.$suff))
echo "Generic".$suff." Existsn";
}

输出:

creating class GenericPrice
creating class GenericSize
creating class GenericWeight
creating class GenericPriceSize
creating class GenericPriceWeight
creating class GenericSizeWeight
creating class GenericPriceSizeWeight
GenericPrice Exists
GenericSize Exists
GenericWeight Exists
GenericPriceSize Exists
GenericPriceWeight Exists
GenericSizeWeight Exists
GenericPriceSizeWeight Exists
Process finished with exit code 0

最新更新