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

时间:2015-11-12 17:39:20

标签: php oop polymorphism dynamic-typing

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

虽然PHP非常宽容,如果我不混淆产品和吸气剂,我的代码会起作用,但是当我把AssemblyProductA混为一谈时,没有提供任何保护措施,然后使用来自ProductB的getter。

另外,我的IDE确实知道这两个类中的方法,因此当我使用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中自动完成,这很好,但不能解决我的其他问题。此外,我将一些东西拼凑成一个单一的“复合”产品有点怪异......这看起来更像是一种解决方法。在我的具体情况下,我有两个非常相似的产品,在一些微小的东西,如一些属性上有所不同。

真实世界示例

产品型号A和B都有一个工程图,其中列出了用于识别各种产品尺寸的变量。两种产品的命名尺寸相似。例如,产品A上的M1表示产品B上的相同物理尺寸。但是,产品A具有产品B上不存在的特征,反之亦然。

如果仍然感觉太假设,实际图纸上的实际尺寸列为 B2 B3 。这些尺寸(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

目标

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

  • 汇编说“我知道我要列出的命名变量(M1,B2,B3),无论产品型号如何”
  • 产品A说“我只知道我包含的内容。我包含命名的维度A.我不知道B是什么,也不关心
  • 产品B说“我只知道B” ...

2 个答案:

答案 0 :(得分:1)

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

get_class($product);

会告诉你哪个班级$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

答案 1 :(得分:0)

我知道已有一个已接受的答案,但我想提供一个更正确的OOP方法。

实际的解决方案只会因为PHP使用实际类型而不是从方法签名类型限制对象的范围。

为避免复杂性分歧,我不建议使用每次添加另一个(新产品类型)时必须在类(程序集)中添加一些代码的代码。

在您的情况下,我会使用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种不同的产品类型,则每种产品类型将独立于其他产品类型(一个类Assembly,一个类Product和一个类Creator)。如果您必须为每个吸气剂编码每个产品的特异性,请想象您的汇编类!