【问题标题】:Polymorphic class methods/properties in php?php中的多态类方法/属性?
【发布时间】:2021-07-16 16:18:16
【问题描述】:

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

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

我能想到的方法:

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

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

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

用例: 我正在考虑为电子商务网站创建产品构建器,并希望将产品的属性分解为抽象的子部分。有些部件可能会影响产品的最终价格,有些可能会增加产品的最终重量,有些可能会影响产品的尺寸(用于运输)等等。但有些人不会做上述任何事情。 我希望能够在实例化定义产品属性(例如 hasPrice、addWeight、effectsDimensions 等)的子组件类时添加“行为”。我希望我可以有条件地为此使用 Traits,但唉,那不是案子。所以我可能会依赖于将实例附加到类属性,并结合使用接口有条件地检查它们,以确保标准方法在任何附加的行为类中可用。

我只是想知道在我构建我的原型包之前是否有人有任何其他解决方案,以至于很难根据更好的想法回头和重建。

【问题讨论】:

  • 仅供参考,特性是在 2012 年 3 月随 PHP 5.4 引入的。
  • 是的,我在玩特质。我也终于在 laravel 文档中找到了关于变形的部分。但是我能够通过重新排列对象优先级来解决我的大部分问题。

标签: php polymorphism traits


【解决方案1】:

好吧,这是一种笨拙的方法。 (使用 eval - gaaaahhh!)

<?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;\n\n", CLASS_NAMESPACE)
                    . $behaviorUse
                    . "\nclass " . $className . " extends ComponentAbstract\n{"
                    . $behaviorCode
                    . "\n    public function __construct(\$nameOrSpec=\"Generic " . implode(" ", $classNameParts) . " Component\", string \$version=null)\n"
                    . "    {\n"
                    . "        parent::__construct(\$nameOrSpec, \$version);\n\n"
                    . $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." Exists\n";
}

输出:

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

【讨论】:

  • 然后您修改自动加载器以在需要时调用该函数来创建类。 (只是为了让一些未来的开发人员在必须弄清楚你的代码在做什么时精神崩溃!哈哈)
猜你喜欢
  • 2013-03-28
  • 2012-02-25
  • 1970-01-01
  • 1970-01-01
  • 2013-04-14
  • 1970-01-01
  • 2021-02-14
  • 2023-02-07
相关资源
最近更新 更多