Buy Access to Course
05.

Creating an Abstract Ship

Share this awesome video!

|

Keep on Learning!

There is one more thing that is special about the Rebel Ships. Since, they're the good guys we're going to give them some extra Jedi power.

Inside of Ship we have a jediFactor which is a value that is set from the database and a getJediFactor() function:

140 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship
{
// ... lines 5 - 10
private $jediFactor = 0;
// ... lines 13 - 89
public function getJediFactor()
{
return $this->jediFactor;
}
// ... lines 94 - 138
}

In the BattleManager this is used to figure out if some super awesome Jedi powers are used during the battle.

For Rebel Ships, the Jedi Powers work differently than Empire ships. They always have at least some Jedi Power, sometimes there's a lot and sometimes it's lower, depending on what side of the galaxy they woke up on that day. So, instead of making this a dynamic value that we set in the datbase let's create a public function getJediFactor() that returns the rand() function with levels between 10 and 30:

36 lines | lib/Model/RebelShip.php
// ... lines 1 - 2
class RebelShip extends Ship
{
// ... lines 5 - 30
public function getJediFactor()
{
return rand(10, 30);
}
}

Setting it up like this overrides the function in the Ship parent class.

Back in the browser, when we refresh we can see the Jedi Factor keeps changing on the first two Rebel ships only.

Fat Classes

Over in PhpStorm, when we look at this function now, Ship has a Jedi Factor property but RebelShip doesn't need that at all. Since RebelShip is extending Ship it is still inheriting that property. While this doesn't hurt anything it is a bit weird to have this extra property on our class that we aren't using at all. And this is also true for the isFunctional() method. In RebelShip it's always true:

36 lines | lib/Model/RebelShip.php
// ... lines 1 - 2
class RebelShip extends Ship
{
// ... lines 5 - 17
public function isFunctional()
{
return true;
}
// ... lines 22 - 34
}

But in Ship it reads from an underRepair property, and again that's just not needed in RebelShip:

140 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship
{
// ... lines 5 - 14
private $underRepair;
// ... lines 16 - 23
public function isFunctional()
{
return !$this->underRepair;
}
// ... lines 28 - 138
}

The point being, Ship comes with extra stuff that we are inheriting but not using in RebelShip.

These classes are like blueprints, so maybe, instead of having RebelShip extend Ship and inherit all these things it won't use, we should have a third class that would hold the properties and methods that actually overlap between the two called AbstractShip. From here, Ship and RebelShip would both extend AbstractShip to get access to those common things.

This is a way of changing the class heirachy so that each class has only what it actually needs.

Creating an AbstractShip

Let's start this! Create a new PHP Class called AbstractShip:

140 lines | lib/Model/AbstractShip.php
// ... lines 1 - 2
class AbstractShip
{
// ... lines 5 - 138
}

Since it is the most abstract idea of a ship in our project. To start, I'm going to copy everything out of the Ship class and paste it into AbstractShip:

140 lines | lib/Model/AbstractShip.php
// ... lines 1 - 2
class AbstractShip
{
private $id;
private $name;
private $weaponPower = 0;
// ... lines 10 - 16
public function __construct($name)
{
$this->name = $name;
// randomly put this ship under repair
$this->underRepair = mt_rand(1, 100) < 30;
}
public function isFunctional()
{
return !$this->underRepair;
}
// ... lines 28 - 138
}

I know this looks like where we just were, but trust me we're going somewhere with this.

Now, let's write Ship extends AbstractShip:

6 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
}

And do the same thing in RebelShip changing it from Ship to AbstractShip:

36 lines | lib/Model/RebelShip.php
// ... lines 1 - 2
class RebelShip extends AbstractShip
// ... lines 4 - 36

Then in bootstrap add our require line for our new class:

16 lines | bootstrap.php
// ... lines 1 - 9
require_once __DIR__.'/lib/Model/AbstractShip.php';
require_once __DIR__.'/lib/Model/Ship.php';
require_once __DIR__.'/lib/Model/RebelShip.php';
// ... lines 13 - 16

Perfecto!

After just that change, refresh the browser and see what's happening. Hey nothing is broken, which makes sense since nothing has really changed in our code's functionality -- yet.

Let's trim down AbstractShip to only the items that are truly shared between our two ships.

First, jediFactor is specific to Ship so let's move it over there:

23 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
private $jediFactor = 0;
// ... lines 6 - 21
}

And then we'll update the references to it in AbstractShip to what the two classes share, which is a getJediFactor() function:

122 lines | lib/Model/AbstractShip.php
// ... lines 1 - 2
class AbstractShip
{
// ... lines 5 - 50
public function getNameAndSpecs($useShortFormat = false)
{
if ($useShortFormat) {
return sprintf(
'%s: %s/%s/%s',
$this->name,
$this->weaponPower,
$this->getJediFactor(),
$this->strength
);
} else {
return sprintf(
'%s: w:%s, j:%s, s:%s',
$this->name,
$this->weaponPower,
$this->getJediFactor(),
$this->strength
);
}
}
// ... lines 71 - 120
}

So let's copy and paste that function into Ship:

23 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
// ... lines 5 - 9
public function getJediFactor()
{
return $this->jediFactor;
}
// ... lines 14 - 21
}

RebelShip already has one so that class is good to go already. Now in AbstractShip the getJediFactor() function will either call the version of the function in Ship or RebelShip depending on what is being loaded. There are a few other things I want to share with you about this, but we'll get to those later.

Now let's move setJediFactor() from AsbtractShip into Ship:

23 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
// ... lines 5 - 17
public function setJediFactor($jediFactor)
{
$this->jediFactor = $jediFactor;
}
}

and that should do it! Now, Ship still has all the functionality that it had before, it extends AbstractShip, and only contains its unique code. And RebelShip no longer inherits the jediFactor property and anything that works with it. Now each file is simpler, and only has the code that it actually needs. Back to the browser to test that everything still works. Oh look an error!

Call to undefined method RebelShip::setJediFactor() on ShipLoader line 55.

Let's check that out.

Ah, it's because down here when we create a ship object from the database, we always call setJediFactor() on it, and that doesn't make sense anymore. So we'll move this up and only call it for the Ship class:

79 lines | lib/Service/ShipLoader.php
// ... lines 1 - 2
class ShipLoader
{
// ... lines 5 - 44
private function createShipFromData(array $shipData)
{
if ($shipData['team'] == 'rebel') {
$ship = new RebelShip($shipData['name']);
} else {
$ship = new Ship($shipData['name']);
$ship->setJediFactor($shipData['jedi_factor']);
}
$ship->setId($shipData['id']);
$ship->setWeaponPower($shipData['weapon_power']);
$ship->setStrength($shipData['strength']);
return $ship;
}
// ... lines 60 - 76
}
// ... lines 78 - 79

Refresh again, no error, perfect!

Back to AbstractShip, we have the underRepair property which is only used by Ship, so let's move that over:

38 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
// ... lines 5 - 6
private $underRepair;
// ... lines 8 - 32
public function isFunctional()
{
return !$this->underRepair;
}
}

And, let's also move over the isFunctional() method from AbstractShip as well, since RebelShip has its own isFunctional() method already. Finally, the last place that this is used is in the construct function. The random number for under repair is set here, so just remove that one piece but leave the $this->name = $name; where it is since it is shared by both types of ships. In the Ship class we'll override the construct function, I'll keep the same argument. Using our trick from earlier I'll call the parent::__construct($name); and then paste in the under repair calculation line:

38 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
// ... lines 5 - 8
public function __construct($name)
{
parent::__construct($name);
// ... lines 12 - 13
$this->underRepair = mt_rand(1, 100) < 30;
}
// ... lines 16 - 36
}

The last thing that's extra right now in the AbstractShip class is the getType() method. Both ships need a getType() function, but this one here is specific to the Ship class so we'll cut and paste that over:

43 lines | lib/Model/Ship.php
// ... lines 1 - 2
class Ship extends AbstractShip
{
// ... lines 5 - 37
public function getType()
{
return 'Empire';
}
}

Back to the browser and refresh, everything looks great. The Rebel Ships aren't breaking and Jedi Factors are random, awesome!

This is the same functionality we had a second ago but the RebelShip class is a lot simpler. It only inherits what it actually uses from AbstractShip. Which means that our new class truly is the blueprint for the things that are shared by all the ship classes. Ship extends AbstractShip as does RebelShip and then each add their own specific code.

While this isn't a new concept, it is a new way of thinking of how to organize your "class hierarchy".