Composite pattern in PHP, how to design classes to work around the need to extend two classes
Asked Answered
I

2

1

I'm using the composite pattern to have reusable elements to build a page. for this i have a simple interface which drives the pattern

interface Ai1ec_Renderable {
    /**
     * This is the main function, it just renders the method for the element,
     * taking care of childrens ( if any )
     */
    public function render();
}

Since only some of the elements will be allowed to have children, i have created an abstract class that adds that behaviour

abstract class Ai1ec_Can_Have_Children {
    /**
     *
     * @var array
     */
    protected $renderables = array();
    /**
     * Adds a renderable child to the element
     * 
     * @param Ai1ec_Renderable $renderable
     */
    public function add_renderable_children( Ai1ec_Renderable $renderable ) {
        $this->renderables[] = $renderable;
    }
}

so if the object can have children it extends the abstract class. The problem arise because i have a second abstract class for html elements

abstract class Ai1ec_Html_Element {
    /**
     *
     * @var string
     */
    protected $id;
    /**
     *
     * @var array
     */
    protected $classes = array();
    /**
     *
     * @param $id string        
     */
    public function set_id( $id ) {
        $this->id = $id;
    }

    public function add_class( $class ) {
        $this->classes[] = $class;
    }
    protected function create_class_markup() {
        if (empty( $this->classes )) {
            return '';
        }
        $classes = implode( ' ', $this->classes );
        return "class='$classes'";
    }
    protected function create_attribute_markup( 
        $attribute_name, 
        $attribute_value
    ) {
        if (empty( $attribute_value )) {
            return '';
        }
        return "$attribute_name='$attribute_value'";
    }
}

The problem is that all the objects of the composite must implement the interface, some of them must extend only the first class ( can_have_children ) but not the second ( this is because they are higher level abstraction that configure another renderable object to do the job ), some of them the second but not the first and some of them both classes.

Have i done somthing wrong while designing my classes?How do i come out of it? The most obvious way is to make a class duplicating some code

abstract class Ai1ec_Can_Have_Children extends Ai1ec_Html_Element {
    /**
     *
     * @var array
     */
    protected $renderables = array();
    /**
     * Adds a renderable child to the element
     * 
     * @param Ai1ec_Renderable $renderable
     */
    public function add_renderable_children( Ai1ec_Renderable $renderable ) {
        $this->renderables[] = $renderable;
    }
}

this would work, but it's a smell that something is not right, as i would need to duplicate code if i add something to Can_Have_Children. What should i do?

P.S. No traits, i support 5.2

Inutility answered 10/8, 2012 at 20:11 Comment(4)
0 down vote why do Ai1ec_Html_Element and Ai1ec_Can_Have_Children not implement Ai1ec_Renderable?Waterfowl
@Raidenace that's not the problem, i need to combine the functionality of the two abstract classes. Usually when this happens means that you did something wrong with the design.Inutility
To my eyes, based on the architecture you have, I think it is more of a case of you having chosen the wrong design pattern to implement your solution. If you have created an implementation as close as you can to a design pattern, and then you find it necessary to kludge it, its usually a case of wrong pattern selection, not wrong pattern implementation. Maybe you should check if there are patterns better aligned to solve your issue..Waterfowl
@Raidenace that might be, if you have better options to suggest i would be happy to hear :)Inutility
C
3

It seems there are two major responsibilities here: rendering and having children. So I would start with two separate interfaces:

interface Renderable {
  public function render();
}

interface Container {
  public function addChild(Renderable $renderable);
}

Then have a concrete implementation of Container (not abstract):

class BasicContainer implements Container, Renderable {
  /* similar to your Ai1ec_Can_Have_Children class */
}

Then you can define your HTML element classes as follows, using composition instead of inheritance for adding container functionality:

abstract class HtmlElement implements Renderable {
  /* similar to your Ai1ec_Html_Element class */
}

abstract class ContainerHtmlElement extends HtmlElement implements Container {
  private $container = new BasicContainer();

  public function addChild(Renderable $renderable) {
    $this->container->addChild($renderable);
  }

  public function render() {
    parent::render();
    $this->container->render();
  }
}
Clapperclaw answered 11/8, 2012 at 23:49 Comment(0)
I
0

Two important Design Pattern principles can be found here:

  1. Program to an interface and not an implementation
  2. Favor object composition over inheritance

If you look at the original class diagram in the GoF book, you'll see that the Component interface has two implementations: Leaf and Composite. The Leaf participant only implements the Operation() and none of the others. For example, consider the following:

    <?php
class Leaf implements IComponent
{
    private $sName;
    public function __construct($sNodeName)

    {
        $this->sName=$sNodeName;
    }

    /* None of this batch of methods are used by Leaf */
    /* However in order to correctly implement the interface */
    /* you need some kind of implementation */
    public function add(IComponent $comOn){}
    public function remove(IComponent $comGone){}
    public function getChild($someInt){}

    /* Some userful content is required for the operation */
    public function operation()
    {
        echo $this->sName . "<br />";
    }
}

?> 

So while the Leaf participant in the pattern only implements the operation() method, it has dummy implementations of the other interface methods. You can find more details at:

http://www.php5dp.com/the-composite-design-pattern-in-php-part-i-from-conceptual-to-practical/

and

http://www.php5dp.com/category/design-patterns/composite/

The Composite is one of the few where you'll find this kind of "one-wing" implementation.

Ixion answered 30/7, 2014 at 12:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.