PHP delegation vs. GoLang type embedding

Photo by Martin Reisch on Unsplash

One of the things I like the most about the Go programming language that I really wish PHP had is its type embedding.

And while legitimate Gophers know that Go is not exactly an object-oriented language, it has many features that allow leveraging OOP development experience so please permit me to discuss type embedding from an OOP perspective so it will be easier to understand by OOP developers.

PHP composition using delegation

In OOP languages like PHP, we have “inheritance” which allows classes to use the functionality of a more abstract class without having to implement it.

Consider a Block class for a desktop app with content properties and a render() method. With inheritance a developer can declare a AdvertBlock class which can inherit from the Block class and will automatically gain the Block‘s content property.

class Block {
    public $content;
    function render() {
        echo $this->content;
    }
}
class AdvertBlock extends Block {
    function __construct($advert) {
        $this->content = $advert;
    }
}
$avb = new AdvertBlock("<div class="ad"></div>");
$avb->render();

But in recent years, “thought leaders” in the development world have decided that Inheritance is bad and can no longer be discussed in polite company. The new king is Composition, and it is what everyone should be doing.

The problem is, if you use composition instead of inheritance in most OOP languages –– like PHP –– you end up having to write lots of methods that do nothing other than delegate from the contained class (e.g. Block) to the classes contain it (e.g. AdvertBlock), and you have to somehow provide an instance of the contained class when you instantiate it (as you see here in __construct()):

class AdvertBlock {
    private $_block;
    function __construct($block,$advert) {
        $this->_block = $block;
        $this->content = $advert;
    }
    function render() {
        $this->_block->render();  
    }
}
$avb = new AdvertBlock(new Block(), "<div class="ad"></div>");
$avb->render();

Now the above might not look so bad, but consider when you are dealing with tens of classes and and tens of methods for each class where you need to implement this delegation. That is a lot of boilerplate code to write (that, frankly, the language should handle for you.)

Worse consider when you add a method to one of your contained classes? Now you have to go back to each containing class and add a delegate method as well. Ugh.

Of course in PHP you can actually delegate automatically with the __call()magic method, but that is tedious to implement in all your classes, it has significant performance penalties to consider, and you still have to add a PHPDoc comment if you want your IDE to provide autocomplete and to stop flagging as an error. (And I did not even mention delegating properties via __get() and __set() methods!):

/**
 * @method render() void
 */
class AdvertBlock {
    private $_block;
    function __construct($block,$advert) {
        $this->_block = $block;
        $this->content = $advert;
    }
    function __call($method, $args) {
        $value = null;
        foreach( get_object_vars($this) as $name => $value ) {
            if ( method_exists( $this->$name, $method ) ) {
                $value = call_user_func_array([$this->$name,$method],$args);
                break;
            }
        }
        return $value;
    }
}

Still, even with all these downsides I still use this approach in all of my PHP projects because it is mostly better than not.) Ironically, one of the best ways to handle including this type of __call()method in all your PHP classes is to use some sort of BaseObject() class that you have all your other classes extend from. But I digress…

Go composition using type embedding

Once again, Go is not really an object-oriented language; one of the main things it does not support is inheritance. It also does not have classes, although it has structs which are a reasonable facsimile for those coming to Go from other OOP languages.

Since Go does not provide inheritance you must use composition for any sharing of functionality across structs. But here is where Go shines with its type embedding. Go provides automatic delegation when you embed at type; you do not have to maintain it like you do with PHP.

Let us revisit our simple Block and AdvertBlock example, this time using Go:

package main
type Block struct {
    Content string
}
func (b Block) render() {
    print(b.Content)
}
type AdvertBlock struct {
    Block
}
func NewAdvertBlock(a string) AdvertBlock {
    return AdvertBlock{Block{Content:a}}
}
func main(){
    avb := NewAdvertBlock("<div class=\"ad\"></div>")
    avb.render()
}

Easy, peasy!

Even better, you can run it in the Go Playground and/or iyou can tweak the code to see how it behaves given different code choices. Try changing the Block declaration embedded in AdvertBlock to *Block and see what happens (*Block means “a pointer to a Block.)


Leave a Reply

Your email address will not be published. Required fields are marked *