My goal was to be able to create an object which is composed by other objects without having to know beforehand what these objects were, then do checks to see if they actually exist, then add them to my collection and so on -- avoid all that fuss.
My use-case is that I have a Tooltip
which I output to the markup. Now, this object is parsed by a higher-level Generator
. This Generator
looks inside the Tooltip
and says "aha! you have a StyleInterface
object (behavior), and so, I will include a CSS file for your" and so on. My goal is to allow people to add functionality to an object without enforcing anything. A friendly way, through code of saying "if you wanna add this, you can, but you don't have to."
And so, the Composite
pattern was born.
I came up with it from here: Scaling inserting related optional objects to your collection
Yet, there is a problem that's appeared in this process: because it's PHP, I cannot type-hint that an array's members must be of a certain type, ComposableInterface
in my case.
My pattern has achieved "syntactic sugar niceness", even if I honestly believe it's over-engineered and its goals were met:
- Signal to the outside world that it is dynamic: it is composed of a dynamic number of behaviors (objects).
- Make the insertion easy so you no longer have to name every single component in the constructor, as well as add it manually as in my original question.
Each item that can be inside of a Composite
is called a Component
and it must respect the following interface:
interface ComposableInterface
{
public function getComposableName();
}
We identify it by getComposableName
. Now, who ingests all these Composable
s? It's, as I said, the Composite
:
interface CompositeInterface
{
public function getComponents();
}
Let's build the Composite
:
/**
* An object which can be composed of multipel behaviors.
*/
class Composite implements CompositeInterface
{
/**
* @var array
*/
private $components = [];
public function __construct( $components = [] )
{
foreach( $components as $component ) {
$this->components[$component->getComposableName()] = $component;
}
}
/**
* Retrieve the components.
*
* @return array
*/
public function getComponents()
{
return $this->components;
}
}
Let's try to use it by first creating some Composables
components/behaviors that our main object can have:
interface Style extends ComposableInterface{}
/**
* A component / behavior that can be part of a composite.
*/
class StyleOne implements Style
{
public function getComposableName()
{
return 'style';
}
}
interface Markup extends ComposableInterface{}
/**
* A component / behavior that can be part of a composite.
*/
class MarkupOne implements Markup
{
public function getComposableName()
{
return 'markup';
}
}
interface Categories extends ComposableInterface{}
/**
* A component / behavior that can be part of a composite.
*/
class CategoriesOne implements Categories
{
public function getComposableName()
{
return 'categories';
}
}
Sweet, we have 3 behaviors our Composite
can have, let's try to initialize it:
$composite = new Composite([
new StyleOne,
new MarkupOne,
//new CategoriesOne -- not needed, but it can be added!
]);
...and voila. This way, we can take away or add as many behaviors as we want.
So, what exactly did we do and are we over-engineering? I simply want to build a dynamic (in the numbers of behaviors) system that I can re-use and not have to check if a Composable
exists every time I have to work with an entire Composite
object.
With that in mind, we completely avoided having to type-hint our possible components and no longer have to manually check whether a Composable
exists, then get its name, then add it to our collection.
It looks cleaner. But it still has that issue. How can I tell the system that inside my Composite
, I want the array of arguments to be of type ComposableInterface
without having to do a manual check on each component that's passed?
Composite
is dynamic, not only that but theseComposables
are also interchangeable themselves. I am not sure what I created here, I just know that, performance aside, it's nice syntactic sugar that saves me a lot of time.mixin
talk was just very good talk and beneficial (to me at least). I would still like to fix my problem from the question, if it's fixed, then I test this "pattern" and see I don't like it, I can ask about mixin implementations for PHP. For now, the issue is type-hinted array members and / if they're a good idea.