I have a class called Bird that accepts array of birds in the constructor. I am trying to implement a function inside it that will check if any of the birds is currently flying, keeping in mind that all code should comply with SOLID principles.
I have the following two classes (Parrot and Ostrich)
class Parrot extends FlyingBirds{}
class Ostrich extends BirdDetail{}
BirdDetail Class
abstract class BirdDetail {
protected $didNotSleepLastNight;
public function __construct(bool $didNotSleepLastNight)
{
$this->didNotSleepLastNight= $didNotSleepLastNight;
}
public function didNotSleepLastNight(): bool
{
return $this->didNotSleepLastNight;
}
}
FlyingBirds (Not all birds can fly, like ostrich)
abstract class FlyingBirds extends BirdDetail{
protected $isFlyingNow;
public function __construct(bool $didNotSleepLastNight, bool $isFlyingNow)
{
parent::__construct($didNotSleepLastNight);
$this->isFlyingNow = $isFlyingNow;
}
public function isFlyingNow(): bool
{
return $this->isFlyingNow;
}
}
Then I have a class called Bird
class Bird
{
private $details;
public function __construct(array $details)
{
$this->details = $details;
}
public function didNotSleepLastNight(): bool
{
foreach ($this->details as $detail) {
if ($detail->didNotSleepLastNight()) {
return true;
}
}
return false;
}
public function isFlyingNow(): bool
{
foreach ($this->details as $detail) {
if ($detail->isFlyingNow())
{
return true;
}
}
return false;
}
}
Now I am passing instances of Parrot and Ostrich to Bird Constructor
$bird = new Bird([new Parrot(true, false), new Ostrich(false)]);
if($bird->isFlyingNow())
{
echo "Yes";
}
else
{
echo "No";
}
The problem is The above code is giving me the following error
Fatal error: Uncaught Error: Call to undefined method Ostrich::isFlyingNow()
That is because Ostrich/BirdDetail class does not have a method called isFlyingNow.
The problem can be fixed by the replacing the isFlyingNow method in Birds class with the following code:
public function isFlyingNow(): bool
{
foreach ($this->details as $detail) {
if (method_exists($detail, 'isFlyingNow') && $detail->isFlyingNow())
{
return true;
}
}
return false;
}
Could you please tell me if the above fix is some sort of violation of SOLID principles? Or can the problem be solved in a better way?
Not really breaking SOLID, but simply not very well designed.
For a neater approach, use interfaces.
Since not all birds can fly, having a public facing method isFlying()
for all birds seems rather wasteful.
Maybe you could do something like this:
Your base Bird
class, where you'd define the common properties and methods for all birds.
class Bird {
}
Then, different interfaces to describe different possible behaviours of Bird subclasses. Some birds fly, some swim, some talk, some sing, some dive, some can run, etc, etc.
For the flyers:
interface FlyingBird {
function isFlying():bool;
function takeOff();
function land();
function fly($bearing, $distance);
}
For the talkers:
interface TalkingBird {
function say($something);
}
The singers:
interface SingingBird {
function sing(array $notes);
}
The swimmers (you may need to differentiate those that swim on the surface of the water and those that can dive underneath the surface).
interface SwimmingBird {
function isSwimming(): bool;
// etc
}
For the runners:
interface RunningBird {
function isRunning(): bool;
// etc
}
Then you could have classes like Parrot
(flies and talks, but doesn't do singing, running, or swimming)
class Parrot extends Bird implements TalkingBird, FlyingBird {
// todo: actual implementation
}
Or Ostrich
(can run, but doesn't swim, sing, talk or fly):
class Ostrich extend Birds implements RunningBird { /* implementation */}
Or even Penguin
(can swim, can't fly, can't run, can't sing, can't talk):
class Penguin extends Bird implements SwimmingBird { /* implementation */ }
Etc, etc. The whole @package Ornithology
is taking shape.
Users of these classes should check if the instances are implementing the appropriate interfaces:
if ($birdInstance instanceof FlyingBird && $birdInstance->isFlying()) {
echo "This bird is flying!";
}
To simplify the composing of these classes you may want to create some traits:
E.g.
trait FlyingBirdTrait {
private $flying = false;
function isFlying():bool {
return $this->flying;
}
function takeOff() {
$this->flying = true;
}
function land() {
$this->flying = false;
}
function fly($bearing, $altitude, $distance) {
if (!$this->isFlying()) {
$this->takeOff();
}
// calculate new position;
}
}
Which then classes like Parrot
could use:
class Parrot extends Bird implements TalkingBird, FlyingBird {
uses FlyingBirdTrait;
// rest of the implementation, etc;
}