使用多态性时,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,我的代码会工作,但是当我搞砸和填充时没有提供保护Assembly
与 ProductA
,但随后使用 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 上没有的功能,反之亦然。
如果这仍然感觉过于假设,实际图纸上的实际尺寸列为 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
目标
目标是让单个 class(程序集)负责列出命名维度变量,并且每个产品只知道自己。所以
- 程序集说 "I know everything about the named variables I want to list (M1, B2, B3), regardless of product model"
- 产品 A 说“我只知道我包含的内容。我包含命名维度 A。我不知道 B 是什么,我也不关心
- 产品 B 说 "I only know B"
...
我觉得这不是正确的解决方案,但您的要求可以通过以下方式实现。
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();
}
记录在:
我知道已经有一个可接受的答案,但我想给出一个更正确的 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 编码每个产品的特殊性!
我有一个名为 Assembly
的 class,它隐藏了基础产品的实现。 ProductA
可以有一组 getter,ProductB
可以有另一组。
虽然 PHP 非常宽容,如果我不混淆 Products 和 getters,我的代码会工作,但是当我搞砸和填充时没有提供保护Assembly
与 ProductA
,但随后使用 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 上没有的功能,反之亦然。
如果这仍然感觉过于假设,实际图纸上的实际尺寸列为 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
目标
目标是让单个 class(程序集)负责列出命名维度变量,并且每个产品只知道自己。所以
- 程序集说 "I know everything about the named variables I want to list (M1, B2, B3), regardless of product model"
- 产品 A 说“我只知道我包含的内容。我包含命名维度 A。我不知道 B 是什么,我也不关心
- 产品 B 说 "I only know B" ...
我觉得这不是正确的解决方案,但您的要求可以通过以下方式实现。
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();
}
记录在:
我知道已经有一个可接受的答案,但我想给出一个更正确的 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 编码每个产品的特殊性!