DRY stands for “Don’t Repeat Yourself” and basically means avoid duplicating logic in your code.
There are several ways of doing this. Which way to not repeat yourself depends on how the logic is used.
For example:
<?php
namespace App\NotSoCleanCode;
class ClassWithDuplicateCode
{
private $statuses;
public function getSomeStatus()
{
// sort existing statuses
$sortedStatuses = [];
for ($i = count($this->statuses); $i >= 0; $i--) {
$sortedStatuses[] = $this->statuses[$i];
}
return $sortedStatuses[3];
}
public function getOtherStatus()
{
// sort existing statuses
$sortedStatuses = [];
for ($i = count($this->statuses); $i >= 0; $i--) {
$sortedStatuses[] = $this->statuses[$i];
}
return $sortedStatuses[4];
}
}
The above example shows two methods that contain duplicated logic. So the author is repeating himself.
We could easily refactor this class to:
<?php
namespace App/CleanCode;
class ClassWithoutDuplicateCode
{
private $statuses;
private function sortStatuses()
{
$sortedStatuses = [];
for ($i = count($this->statuses); $i >= 0; $i--) {
$sortedStatuses[] = $this->statuses[$i];
}
return $sortedStatuses;
}
public function getSomeStatus()
{
$sortedStatuses = $this->sortStatuses();
return $sortedStatuses[3];
}
public function getOtherStatus()
{
$sortedStatuses = $this->sortStatuses();
return $sortedStatuses[4];
}
}
Not only is the code a lot more expressive (no longer needing the comment to explain what the code does), it is also shorter.
This works perfectly well for classes that have duplicate logic within the class itself. But how about when multiple classes need the same logic?
In that case we have to make a decision, there are at least two ways to solve the problem.
The first solution that comes to mind is applying polymorphism.
Using this approach you would make an “abstract” class and place the shared logic in the abstract class. This way it is available in all subclasses.
For example:
<?php
namespace App\CleanCode;
abstract class Fruit
{
protected $hasSkin = true;
public function peel()
{
if ($this->hasSkin) {
$this->hasSkin = false;
return true;
}
return false;
}
}
Now if we have the class “Orange” and “Apple” we can “peel” both of them, without repeating ourselves:
<?php
namespace App\CleanCode;
class Orange extends Fruit
{
public function prepareForEating()
{
return $this->peel();
}
}
<?php
namespace App\CleanCode;
class Apple extends Fruit
{
private $hasCore = true;
private function removeCore()
{
$this->hasCore = false;
}
public function prepareForEating()
{
$this->removeCore();
return $this->peel();
}
}
While this works fine for classes where it is logical that they share a parent class, it does not work very well for classes that are not very related to each other.
Let’s say we have a class called Foot and it represents the human body part. This also has a peelable skin and we can peel it in the same way (codewise). However Foot extends HumanBodyPart and not Fruit.
So how can we share the logic when we are not able or willing to apply polymorphism?
In this case we can use “Composition over inheritance”. What does that mean?
Well it means that we separate the logic to its own class and we use it in different classes.
In this case we would want to make a “Peeler” class and a “Peelable” interface.
For example:
<?php
namespace App\CleanCode;
interface Peelable
{
public function hasSkin();
public function setHasSkin($hasSkin);
}
<?php
namespace App\CleanCode;
class Peeler
{
private $peelable;
public function __construct(Peelable $peelable)
{
$this->peelable = $peelable;
}
public function peel()
{
if ($this->peelable->hasSkin()) {
$this->peelable->setHasSkin(false);
return true;
}
return false;
}
}
<?php
namespace App\CleanCode;
class Orange extends Fruit implements Peelable
{
private $hasSkin = true;
public function hasSkin()
{
return $this->hasSkin;
}
public function setHasSkin($hasSkin)
{
$this->hasSkin = $hasSkin;
}
public function prepareForEating()
{
$peeler = new Peeler($this);
return $peeler->peel();
}
}
<?php
namespace App\CleanCode;
class Foot extends HumanBodyPart implements Peelable
{
public function hasSkin()
{
return $this->exteriorProperties->hasSkin;
}
public function setHasSkin($hasSkin)
{
$this->exteriorProperties->hasSkin = $hasSkin;
}
public function peel()
{
$peeler = new Peeler($this);
return $peeler->peel();
}
}
This way the behavior is separated and the behavior can be applied to other kinds of classes.