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
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
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
Partner Zones AWS Cloud
by AWS Developer Relations
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
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Replace Type Code with State or Strategy

Practical PHP Refactoring: Replace Type Code with State or Strategy

Giorgio Sironi user avatar by
Giorgio Sironi
·
Sep. 28, 11 · Interview
Like (1)
Save
Tweet
Share
8.48K Views

Join the DZone community and get the full member experience.

Join For Free
This article is the third and last part of the refactoring from type codes miniseries.
  • First, we saw a case where no behavior was modified by the type code values: the type code could be substituted by a single class.
  • Second, we saw a case where behavior changed: we substituted the different type codes with subclasses of the original class.
  • In the third case, that we'll see today, some behavior depends on the type code but extending the current class is not possible (because subclassing has been already been used, or as a design choice). This refactoring uses composition instead of inheritance.

What changes from the other two refactorings?

We can make a quick comparison with the other two solutions.

By replacing the type code with a single class, you already use composition and define a type replacing the type code. That type now could be an interface or abstract class, while it was a concrete class in the first refactoring.

By replacing the type code with subclasses, you are using multiple subtypes to model different behavior, eliminating conditional structures like ifs and switch. But in this refactoring they will be subtypes of an external class, not of the original one.

The class you create representing the type code is an implementation of the State or Strategy pattern. In the State case, the object changes often its class, returning a new instance. In the Strategy case, the object changes rarely, and the different implementations contain different algorithms or policies.

Why composing type code objects?

A prerequisite for this refactoring is that different behaviors should be dependent on the type code value.
Moreover, one or more of these conditions should apply:
  • subclassing is not available due to an already existing hierarchy, or not self-explaining as the class names are unclear and sound forced.
  • The life cycle of the object is not on your control, so you can't instantiate the subclasses.
  • The type code changes often, and reinstantiating the right class is a mess for technical reasons (such as your framework or your ORM).

Steps

The steps for applying this refactoring are similar to the Replace Type Code with Subclasses case, but this time they have to be applied to the State or Strategy object instead that on the main class.
  1. First, you have to self-encapsulate the type code.
  2. Then, create a new class as the State object. Start to apply the previous refactoring on it, which means...
  3. Add a subclass of the State object for each value of the type code.
  4. The getTypeCode() in the original class should be delegated to the state object, which has to be created and saved in a field. Each subclass should override the method by providing its own value.
Now you can move logic into the various substates. The actual value of the type code can be removed if not used for displaying or not queried at all.

Example

In the initial state, there is some business logic identical to the Replace Type Code with Subclasses example, which depends on the type code value.
<?php
class ReplaceTypeCodeWithState extends PHPUnit_Framework_TestCase
{
    public function testAnUserCanBeANewbie()
    {
        $user = new User("Giorgio", User::NEWBIE);
        $this->assertEquals("Giorgio", $user->__toString());
    }

    public function testAnUserCanBeRegardedAsAGuru()
    {
        $user = new User("Giorgio", User::GURU);
        $this->assertEquals("ADMIN: Giorgio", $user->__toString());
    }
}

class User
{
    const NEWBIE = 'N';
    const GURU = 'G';

    private $name;
    private $rank;

    public function __construct($name, $rank)
    {
        $this->name = $name;
        $this->rank = $rank;
    }

    public function __toString()
    {
        if ($this->rank == self::GURU) {
            return "ADMIN: $this->name";
        }
        // self::NEWBIE
        return $this->name;
    }
}
We start with self-encapsulation of the rank type code.
class User
{
    const NEWBIE = 'N';
    const GURU = 'G';

    private $name;
    private $rank;

    public function __construct($name, $rank)
    {
        $this->name = $name;
        $this->rank = $rank;
    }

    protected function getRank()
    {
        return $this->rank;
    }

    public function __toString()
    {
        if ($this->getRank() == self::GURU) {
            return "ADMIN: $this->name";
        }
        // self::NEWBIE
        return $this->name;
    }
}
We add a Rank class.
class Rank
{
    public abstract function getCode();
}
We add the subclasses NewbieRank and GuruRank, and then transform the private field into a Rank instance instead of a string type code.
class User
{
    const NEWBIE = 'N';
    const GURU = 'G';

    private $name;
    private $rank;

    public function __construct($name, Rank $rank)
    {
        $this->name = $name;
        $this->rank = $rank;
    }

    protected function getRank()
    {
        return $this->rank->getCode();
    }

    public function __toString()
    {
        if ($this->getRank() == self::GURU) {
            return "ADMIN: $this->name";
        }
        // self::NEWBIE
        return $this->name;
    }
}

class Rank
{
    public abstract function getCode();
}

class NewbieRank
{
    public function getCode()
    {
        return User::NEWBIE;
    }
}

class GuruRank
{
    public function getCode()
    {
        return User::GURU;
    }
}
We can now move the variable logic of __toString() into the subclasses. Article updated: the Rank subclasses were missing the extends keyword.
<?php
class ReplaceTypeCodeWithState extends PHPUnit_Framework_TestCase
{
    public function testAnUserCanBeANewbie()
    {
        $user = new User("Giorgio", new NewbieRank);
        $this->assertEquals("Giorgio", $user->__toString());
    }

    public function testAnUserCanBeRegardedAsAGuru()
    {
        $user = new User("Giorgio", new GuruRank);
        $this->assertEquals("ADMIN: Giorgio", $user->__toString());
    }
}

class User
{
    const NEWBIE = 'N';
    const GURU = 'G';

    private $name;
    private $rank;

    public function __construct($name, Rank $rank)
    {
        $this->name = $name;
        $this->rank = $rank;
    }

    protected function getRank()
    {
        return $this->rank->getCode();
    }

    public function __toString()
    {
        return $this->rank->label() . $this->name;
    }
}

abstract class Rank
{
    public abstract function getCode();
    public abstract function label();
}

class NewbieRank extends Rank
{
    public function getCode()
    {
        return User::NEWBIE;
    }

    public function label()
    {
        return '';
    }
}

class GuruRank extends Rank
{
    public function getCode()
    {
        return User::GURU;
    }

    public function label()
    {
        return 'ADMIN: ';
    }
}

After having moved the variable logic into the Rank hierarchy, we could again remove the getCode() method if not required anymore. We can also turn Rank into an interface, if it is stabilized as a purely abstract class without any code.

PHP

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • All the Cloud’s a Stage and All the WebAssembly Modules Merely Actors
  • Strategies for Kubernetes Cluster Administrators: Understanding Pod Scheduling
  • What’s New in the Latest Version of Angular V15?
  • High-Performance Analytics for the Data Lakehouse

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: