How can PHP code become familiar with certain subtypes of a class passed to it when using polymorphism?

I have a class called Assembly that hides the implementation of the underlying products. ProductA may have one set of getters; ProductB may have another.

While PHP is pretty forgiving, and if I don't mix products and getters, my code will work, but there is no protection offered when I messed up and populated Assembly with ProductA , but then use the getter from ProductB .

In addition, my IDE knows methods from any class, so there is no automatic completion suggested when I work inside Assembly with getters from Product .

I want to get more bulletproof code where Assembly knows or knows which Product subtype it currently hosts. Is there any way to do this?

Example below

 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 Suggestion

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

This gives me autocomplete in my IDE, which is nice, but it doesn't solve my other problems. Also, I'm a little creepy in combining things into a single “composite” product ... It seems to work. In my particular case, I have two very similar products that differ in some minutes in such things as several properties.

Real world example

Both product models A and B have a technical drawing listing variables for defining different product sizes. The named sizes are the same for both products. For example, M1 on product A means the same physical size of product B. However, product A has functions not present on product B, and vice versa.

If this still seems too hypothetical, the actual dimensions of the actual drawing are indicated as B2 and B3. These sizes (B2, B3) are not available on another product model. I want to be able to use the assembly construct to list the variables in the drawing, including M1, B2, B3, etc. I could do it with

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

goal

The goal is to have one class (assembly) responsible for listing the named measurement variables, and each product knows only itself. So

  • The assembly says: "I know everything about the named variables that I want to list (M1, B2, B3), regardless of the product model."
  • Product A says: “I only know what I contained. I use the named dimension A. I don’t know what B is and I don’t care about
  • Product B says: "I only know B" ...
+5
source share
2 answers

I do not think this is the right decision, but what you ask for can be achieved as follows.

 get_class($product); 

Will tell you which class $product is. This will result in the following code:

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

Or:

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

This may result in a check similar to:

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

As documented in:

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

+1
source

I know that there is already an accepted answer, but I would like to give a more correct OOP approach.

The actual solution will work only because PHP uses the real type and does not limit the scope of the object by the type of method signature.

To avoid time differences, I would not recommend a code in which you must add code in the class (assembly) every time you add another (new type of product).


In your case, I would use a template adapter .

Here are your adapters:

 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(); } } 

Here are your adapters:

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

Now you can use the factory template to retrieve your assemblies.

Here are your creators:

 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); } } 

Here you build the factory:

 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); )); } } 

Finally, here is an example program:

 // 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' 

This is a bit more code, but this applies to your method signatures and will handle the growing complexity of your application. With this solution, if you have 100 different types of products, each of them will be independent of the others (one Assembly class, one Product class and one Creator class). Imagine an assembly class if you must code the specifics of each product for each recipient in it!

0
source

Source: https://habr.com/ru/post/1235817/


All Articles