Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclass constructor with a different number of parameters

In my application I have a number of elements that render differently. I've created an abstract class in which they extend.

abstract class Element {

    protected $_value = null;

    public function __construct($value) {
        $this->_value = $value;
    }

    // ...

    public abstract function render();

}

An example of an element might be some text wrapped in a paragraph tag.

class TextElement extends Element {

    public function render() {
        return "<p>{$this->_value}</p>\n";
    }

}

I am having trouble creating elements with more than one value. For example, an image element might render an image tag and include multiple attributes. This is a problem since the constructor in the abstract class only accepts one parameter. I see two possible solutions to this problem. I could pass in an array containing the different attributes to the Element constructor (solution 1) or I could override the constructor in the subclass (solution 2). My question is, which one of these solutions is a better design or does their exist a better solution? Should I be using an interface instead?

Solution 1

class ImageElement extends Element {

    public function render() {
        return "<img src=\"{$this->_value['src']}\" alt=\"{$this->_value['alt']}\" />";
    }

}

$imageElement = new ImageElement(array('src' => '/image.png', 'alt' => 'image'));

Solution 2

class ImageElement extends Element {

    protected $_alt;

    public function __construct($src, $alt) {
        $this->_value = $src;
        $this->_alt = $alt;
    }

    // ...

    public function render() {
        return "<img src=\"{$this->_value}\" alt=\"{$this->_alt}\" />";
    }

}
like image 574
birderic Avatar asked Nov 04 '22 22:11

birderic


2 Answers

With the first solution, if someone where to look at your code they would need to examine the render function to find out what arguments are available.

With the second solution, if your IDE supports autocomplete or if you generated documentation from the source you would be given a list of arguments and you could go with your day quicker.

$value doesn't really tell me much about what it is used for and what if you run into an element that doesnt have ANY parameters? i.e. a BR element or something.

I think solution 2 is more 'correct' but an interface with nothing but a 'render' function is the best solution.

like image 90
Kane Wallmann Avatar answered Nov 09 '22 18:11

Kane Wallmann


Both solutions are sound, but in your case I would pick your first solution. You're dealing with HTML elements here, so that probably means that you are going to support a large number of attributes, many of which are optional. This looks okay:

class ImageElement extends Element {
    public function __construct($src, alt) {...}
}

$img = new ImageElement('/img/src.png', 'alt text');

This is not so nice

class ImageElement extends Element {
    public function __construct($src, $alt, $id = null, $class = null, $width = null, $height = null, $style = null, $title = null) {...}
}

$img = new ImageElement('/img/src.png', 'alt text', null, 'img-class', null, null, null, 'Image title');

Whenever you need to pass multiple arguments, many of which are optional, it's better to pass an object or array to a constructor. This looks much nicer:

$img = new ImageElement(array(
    'src' => '/img/src.png',
    'alt' => 'alt text',
    'class' => 'img-class',
    'title' => 'Image title',
));
like image 35
Sander Marechal Avatar answered Nov 09 '22 16:11

Sander Marechal