Restricting object types that can be added to each other using the Composite Pattern

StackOverflow https://stackoverflow.com/questions/23683731

  •  23-07-2023
  •  | 
  •  

Pregunta

The Situation: I am building a custom PHP application framework. I have implemented a composite pattern so I can build a object tree representing the page to be rendered. Example:

abstract class \Block\BlockAbstract {
     protected _blocks = array();
     public function __construct($properties);
     public function getBlocks();
     public function addBlock($block);

     ...
}

All of my "block" object inherit from this abstract class which is working out great and rendering has been a snap with the help of a little recursion.

The Problem: I need a way to validate what classes of blocks can be added to other blocks. For example I have 4 distinct block types I have to consider. Keep in mind that ALL blocks inherit from a single block abstract, these are classifications of behavior of the concrete classes, not to be confused with additional abstracts:

  • Generic - Generics can have any other block type added to it

  • Final - Can not have any child blocks added to it.

  • Parent - A parent is a block that can only have a specific type child added to it, but can be added to any Generic

  • Child - Can only be added to a specific parent. This block can also share the properties of a Generic, Parent or Final

To sum the goal up: Given a concrete block class name, generate a list of all concrete block classes that can be added to it.

Solutions Proposed So Far:

Add a property called "blockClassification" to the block abstract that defines it as a Generic, Final, Parent or Child and hard code the logic into a validate function that gets called in the from the addBlock($block) method.

I don't like this solution for a number of reasons, but mainly it still doesn't give me a clear path for defining what the concrete parents and children are that can be allowed. For instance, I have a concrete class \Block\Tabs that is a parent. It can only have \Block\Tabs\Panel blocks added to it, and \Block\Tabs\Panel can only be added to \Block\Tabs. Additional properties would have to be added to build those relationships, and those properties could be examined to derive the "block classification" making this approach impractical.

Add allowedChildTypes property to the block that defines a list of concrete classes that can be added. For ease of use 2 values have specific meaning, a * that represents all block types, and NULL value that means no blocks can be added. For all other cases a coma delimited string of class names is provided and only those classes or classes that inherit from them are allowed

I'm leaning towards this direction because PHP gives me the InstaceOf operator. I can use this to do the checks, when I have new concrete classes that extend the base set, that check will return the same results as its parent class. My only hesitation is that while this method definitely solves the problem of "Given a concrete block class name, generate a list of", I feel like it limits me in the future in that I can only do the search down the tree and not back up. I.e. "Given a child block class name, generate a list of parent blocks it can be added to"

I'm sure that I am not the only one to ever run into the problem, so my question to the masses is: What is the best way to approach/solve this problem?

Additional Information:

If you where wondering how I'm getting a list of all the classes in the first place, the framework implements PSR-0 so I am scanning the directory tree to derive all of the class names, then dynamically instantiating them.

$allClassNames = scanDirectories(); // Returns array of strings ex: array('\Block\Classname1','\Block\Classname2'); 
foreach($allClassNames as $className){
  $object = new $className();
}
¿Fue útil?

Solución

Thinking more and more about this, I think there could be another way of abstracting this.

The generic, final, parent and child categories seem to be able to fit in three parts: generic, final and custom (where either parent or child is specific)

And those three parts can be explained with two properties: parent and child.

consider this:

                 parent                child
generic          /                     *
final            /blocks/tabs          null
parent           /blocks/              /blocks/tabs/       
child (generic)  /blocks/tabs/         *
child (final)    /blocks/tabs/         null
child (parent)   /blocks/tabs/         /blocks/popup/
  • generic block has parent /, being the root, and can have any type of child
  • final block is child to /blocks/tabs/ (so the next block example, parent, can have it as child) and cannot have any child
  • parent block can be added to any blocks, and can have child in tabs
  • children can be attached to tabs
  • generic children can have any child
  • final child cannot have any chid
  • parent child can have popup child

given a specific block, you can find general blocks with parent or child property equal to specific parent or child property

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top