DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Inheritance in PHP: A Simple Guide With Examples
  • A Guide to Constructor Chaining in Java
  • The Blue Elephant in the Room: Why PHP Should Not Be Ignored Now or Ever
  • Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL

Trending

  • AI, ML, and Data Science: Shaping the Future of Automation
  • Why Database Migrations Take Months and How to Speed Them Up
  • *You* Can Shape Trend Reports: Join DZone's Software Supply Chain Security Research
  • How to Build Local LLM RAG Apps With Ollama, DeepSeek-R1, and SingleStore
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Replace Inheritance with Delegation

Practical PHP Refactoring: Replace Inheritance with Delegation

By 
Giorgio Sironi user avatar
Giorgio Sironi
·
Jan. 30, 12 · Interview
Likes (0)
Comment
Save
Tweet
Share
12.7K Views

Join the DZone community and get the full member experience.

Join For Free

When a subclass violates the Liskov Substitution Principle, or uses only part of a superclass, it is a warning sign that composition can simplify the design.

Refactoring to composition transform the superclass into an object of its own, which becomes the collaborator of the class under refactoring. Instead of inheriting every public method, the object will just expose the strictly needed methods.

This refactoring is one of the most underused in the PHP world. Don't be afraid to try out composition when you see duplicated code.

Why composition?

The elimination of duplication through inheritance presents some issues.

First, inheritance can be exploited just for code reuse instead of for establishing semantic relationships. Abstract classes with names such as VehicleAbstract, extended by Vehicle, are artificial constructs that do not represent anything in the problem domain.

Moreover, inheritance exposes every public method of the superclass, possibly violating encapsulation. It's only a matter of time before someone calls a method which was not supposed to be available.

The third problem is related to unit testing, and the duplication of test code. Should we test just the subclasses behavior? Or should we test also the inherited features? In the latter case, we will duplicate test code.

Inheritance and delegation (also known as composition) are the two basic relationships between classes in OOP. They are equivalent from a theoretical, functional point of view - but so is a Turing Machine or the whitespace language.

Steps

  1. Create a field in the subclass, and initialize it to $this. It will contain the collaborator.
  2. Change the methods in the subclass to use the delegate field. Methods which are inherited may need to be introduced as a delegation to parent.
  3. Remove the subclass declaration, and replace the delegate with a new instance of the superclass.

Throughout the refactoring, the tests should always pass. This refactoring is crucial as it opens up further possibilities: for example, Dependency Injection performed on the collaborator, or the extraction of an interface containing the public methods called by the former subclass.

Example

We start form the end of the Pull Up Method example: we want to transform the NewsFeedItem superclass into a collaborator with the same behavior.

<?php
class ReplaceInheritanceWithDelegation extends PHPUnit_Framework_TestCase
{
    public function testAPostShowsItsAuthor()
    {
        $post = new Post("Hello, world!", "giorgiosironi");
        $this->assertEquals("Hello, world! -- giorgiosironi",
                            $post->__toString());
    }

    public function testALinkShowsItsAuthor()
    {
        $link = new Link("http://en.wikipedia.com", "giorgiosironi");
        $this->assertEquals("<a href=\"http://en.wikipedia.com\">http://en.wikipedia.com</a> -- giorgiosironi",
                            $link->__toString());
    }
}

abstract class NewsFeedItem
{
    /**
     * @var string  references the author's Twitter username
     */
    protected $author;

    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return $this->displayedText() . " -- $this->author";
    }

    /**
     * @return string
     */
    protected abstract function displayedText();
}

class Post extends NewsFeedItem
{
    private $text;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
    }

    protected function displayedText()
    {
        return $this->text;
    }
}

class Link extends NewsFeedItem
{
    private $url;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }
}

Public methods cannot be inherited from a collaborator, so as a preliminary step they must be delegated to it.

class Post extends NewsFeedItem
{
    private $text;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
    }

    protected function displayedText()
    {
        return $this->text;
    }
}

class Link extends NewsFeedItem
{
    private $url;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }

    public function __toString()
    {
        return parent::__toString();
    }
}

Deciding a name for the role of the collaborator is an important step. It is very likely to change with respect to a name that follows LSP and is used for a superclass. We choose Format, since the parent models a way to print out the author and content fields.

We also extract a method, display(), in the superclass, to split the formatting behavior from the wiring to the fields. We plan to use display() as a collaborator, while __toString() was made for inheritance and will be discontinued.

abstract class NewsFeedItem
{
    /**
     * @var string  references the author's Twitter username
     */
    protected $author;

    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return $this->display($this->displayedText(), $this->author);
    }

    public function display($text, $author)
    {
        return "$text -- $author";
    }

    /**
     * @return string
     */
    protected abstract function displayedText();
}

class Post extends NewsFeedItem
{
    private $text;
    private $format;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
        $this->format = $this;
    }

    protected function displayedText()
    {
        return $this->text;
    }

    public function __toString()
    {
        return parent::__toString();
    }
}

class Link extends NewsFeedItem
{
    private $url;
    private $format;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
        $this->format = $this;
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }

    public function __toString()
    {
        return parent::__toString();
    }
}

We can start using the delegate instead of parent, and of relying on inheritance. __toString() is the only point where we have to intervene:

class Post extends NewsFeedItem
{
    private $text;
    private $format;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
        $this->format = $this;
    }

    protected function displayedText()
    {
        return $this->text;
    }

    public function __toString()
    {
        return $this->format->display($this->displayedText(), $this->author);
    }
}

class Link extends NewsFeedItem
{
    private $url;
    private $format;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
        $this->format = $this;
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }

    public function __toString()
    {
        return $this->format->display($this->displayedText(), $this->author);
    }
}

Now we can eliminate abstract and the abstract method in the superclass, plus the extends keyword in the subclasses. This means now $this->format would be initialized to an instance of TextSignedByAuthorFormat, which is the new name for NewsFeedItem. We also have to push down $this->author.

class TextSignedByAuthorFormat
{
    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return $this->display($this->displayedText(), $this->author);
    }

    public function display($text, $author)
    {
        return "$text -- $author";
    }
}

class Post
{
    private $text;
    private $author;
    private $format;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
        $this->format = new TextSignedByAuthorFormat();
    }

    protected function displayedText()
    {
        return $this->text;
    }

    public function __toString()
    {
        return $this->format->display($this->displayedText(), $this->author);
    }
}

class Link
{
    private $url;
    private $author;
    private $format;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
        $this->format = new TextSignedByAuthorFormat();
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }

    public function __toString()
    {
        return $this->format->display($this->displayedText(), $this->author);
    }
}

Finally, we can simplify part of the code. We delete the __toString() on TextSignedByAuthorFormat which is dead code; and inline the displayedMethod() on Post, which served the inheritance-based solution but now is an unnecessary indirection.

class TextSignedByAuthorFormat
{
    public function display($text, $author)
    {
        return "$text -- $author";
    }
}

class Post
{
    private $text;
    private $author;
    private $format;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
        $this->format = new TextSignedByAuthorFormat();
    }

    public function __toString()
    {
        return $this->format->display($this->text, $this->author);
    }
}

class Link
{
    private $url;
    private $author;
    private $format;

    public function __construct($url, $author)
    {
        $this->url = $url;
        $this->author = $author;
        $this->format = new TextSignedByAuthorFormat();
    }

    protected function displayedText()
    {
        return "<a href=\"$this->url\">$this->url</a>";
    }

    public function __toString()
    {
        return $this->format->display($this->displayedText(), $this->author);
    }
}

There are many further steps we could make:

  • inject the TextSignedByAuthorFormat object. Consequently, if the logic in the collaborator expands we can refactor tests to use a Test Double.
  • Move $this->author into the format.
  • Apply Extract Interface (Format should be the name), to be able to support multiple output formats. Another implementation could place a link on the author too, or could strip all or some of the tags for displaying in a RSS or in a tweet.
PHP Inheritance (object-oriented programming) Delegation (object-oriented programming) Collaborator (software)

Opinions expressed by DZone contributors are their own.

Related

  • Inheritance in PHP: A Simple Guide With Examples
  • A Guide to Constructor Chaining in Java
  • The Blue Elephant in the Room: Why PHP Should Not Be Ignored Now or Ever
  • Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!