使用多态性时,PHP 代码如何知道传递给它的特定 class 子类型?

How can PHP code be made aware of specific class subtypes passed to it, when using polymorphism?

我有一个名为 Assembly 的 class,它隐藏了基础产品的实现。 ProductA 可以有一组 getter,ProductB 可以有另一组。

虽然 PHP 非常宽容,如果我不混淆 Products 和 getters,我的代码会工作,但是当我搞砸和填充时没有提供保护AssemblyProductA,但随后使用 ProductB 中的 getter。

此外,我的 IDE 确实知道 class 中的方法,所以当我在 Assembly 中使用来自 getter 的方法时,没有提供自动完成功能Product.

我想要更多防弹代码,其中 Assembly 知道 知道 其中 Product 当前托管的子类型。有办法吗?

示例如下

class Assembly
{
    private $product;

    public function setProduct(Product $product) {
        $this->product = $product;
    }

    public function getA() {
        return $this->product->getA();
    }

    public function getB() {
        return $this->product->getB();
    }
}

class Product { }

class ProductA extends Product
{
    public function getA() {
        return "A";
    }
}

class ProductB extends Product
{
    public function getB() {
        return "B";
    }
}

//set assembly
$assembly = new Assembly();

//define product sub-type
$product = new ProductB();

//place product into assembly
$assembly->setProduct($product);

//assembly has no clue which subtype it has 
print $assembly->getB(); // "B"
print $assembly->getA(); // prints nothing

接口建议

interface CompositeProductInterface
{
    public function getA();
    public function getB();
}

//update code in Assembly to:
public function setProduct(CompositeProductInterface $product)
{
    $this->product = $product;
}

这让我在 IDE 中自动完成,这很好,但没有解决我的其他问题。此外,我对将各种东西融合到一个 "composite" 产品中有点怪异……这似乎更像是一种变通方法。在我的具体案例中,我有两种非常相似的产品,它们在一些细节上有所不同,比如一些属性。

现实世界的例子

产品模型 A 和 B 都有一个工程图,其中列出了用于标识各种产品尺寸的变量。两种产品的命名维度相似。例如,产品 A 上的 M1 表示产品 B 上的物理尺寸相同。但是,产品 A 具有产品 B 上没有的功能,反之亦然。

如果这仍然感觉过于假设,实际图纸上的实际尺寸列为 B2B3。其他产品型号上没有这些尺寸(B2、B3)。我希望能够使用装配构造在绘图上列出变量,包括 M1、B2、B3 等。我可以用

print $assembly->getM1(); //works for both products
print $assembly->getB2(); //works for one product only
print $assembly->getB3(); //same as above

目标

目标是让单个 class(程序集)负责列出命名维度变量,并且每个产品只知道自己。所以

我觉得这不是正确的解决方案,但您的要求可以通过以下方式实现。

get_class($product);

会告诉你 class $product 是哪个。这将导致以下代码:

private $product;
private $productClass;

public function setProduct(Product $product) {
    $this->product = $product;
    $this->productClass = get_class($product);
}

或:

public function getProductClass(){
    return get_class($this->$product);
}

这可能导致检查类似于:

public function getA() {
    if($this->productClass === "ProductA")
        return $this->product->getA();
}

记录在:

http://php.net/manual/en/function.get-class.php

我知道已经有一个可接受的答案,但我想给出一个更正确的 OOP 方法。

实际的解决方案将起作用只是因为PHP使用真实类型并且不限制方法签名类型的对象范围。

为避免复杂性差异,我不推荐每次添加另一个代码(新产品类型)时都必须在 class(您的程序集)中添加一些代码的代码。


在你的情况下,我会使用 pattern adapter

这是你的适配器:

interface AssemblyInterface 
{
    function getA();
    function getB();
}

class AssemblyA implements AssemblyInterface
{
    private $product;

    public function setProduct(ProductA $product) {
        $this->product = $product;
    }

    public function getA() {
        return $this->product->getA();
    }

    public function getB() {
        return;
    }
}

class AssemblyB implements AssemblyInterface
{
    private $product;

    public function setProduct(ProductB $product) {
        $this->product = $product;
    }

    public function getA() {
        return;
    }

    public function getB() {
        return $this->product->getB();
    }
}

这里是你的适应者:

class ProductA
{
    public function getA() {
        return "A";
    }
}

class ProductB
{
    public function getB() {
        return "B";
    }
}

现在,您可以使用 pattern factory 检索程序集。

这里是您的创作者:

interface AssemblyCreatorInterface
{
    function isSupportingProduct($product);
    function createAssembly($product);
}

class AssemblyCreatorA implements AssemblyCreatorInterface
{
    public function isSupportingProduct($product) {
        return $product instanceof ProductA;
    }

    public function createAssembly($product) {
        return new AssemblyA($product);
    }
}

class AssemblyCreatorB implements AssemblyCreatorInterface
{
    public function isSupportingProduct($product) {
        return $product instanceof ProductB;
    }

    public function createAssembly($product) {
        return new AssemblyB($product);
    }
}

这里是装配厂:

class AssemblyFactory
{
    private $assemblyCreators = array();

    public function addCreator(AssemblyCreatorInterface $assemblyCreator) {
        $this->assemblyCreators = $assemblyCreator;
    }

    public function get($product) {
        // Find the good creator for your product.
        foreach ($this->assemblyCreators as $assemblyCreator) {
            if ($assemblyCreator->isSupportingProduct($product)) {
                // Create and return the assembly for your product.
                return $assemblyCreator->createAssembly($product);
            }
        }

        throw new \InvalidArgumentException(sprintf(
            'No available assembly creator for product "".',
            get_class($product);
        ));
    }
}

最后,这是一个程序示例:

// Process dependency injection.
// (you should be able to do it only once in your code)
$assemblyCreatorA = new AssemblyCreatorA();
$assemblyCreatorB = new AssemblyCreatorB();

$assemblyFactory = new AssemblyFactory();
$assemblyFactory->addCreator($assemblyCreatorA);
$assemblyFactory->addCreator($assemblyCreatorB);

// Retrieve assemblies from products.
$productA = new ProductA();
$productB = new ProductB();

$assemblyA = $assemblyFactory->get($productA);
$assemblyB = $assemblyFactory->get($productB);

$assemblyA->getA(); // 'A'
$assemblyA->getB(); // null
$assemblyB->getA(); // null
$assemblyB->getB(); // 'B'

代码有点多,但它尊重您的方法签名并将处理您的应用程序日益复杂的问题。使用此解决方案,如果您有 100 种不同的产品类型,每一种都将相互独立(一个 class Assembly、一个 class Product 和一个 class Creator)。想象一下您的程序集 class 如果您必须为其中的每个 getter 编码每个产品的特殊性!