How to avoid problems with giving additional behaviours to children classes during changing the inheritanced parent class. What is the problem with Liskov substitution principle implementing new functionalities in the wrong way.
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Why should we use an INTERFACE even when we only have one concrete class?
1. Why should we use an INTERFACE
even when we only have one concrete class?
Based on PHP
by Rafal Ksiazek, IT Leader
https://github.com/harpcio
2. Real example (or maybe not):
Application based on one repository
for an electronics company which has
two departments: one in Europe and one in USA
(Detroit)
Requirements:
…
Sometimes we get an extra job during the day
and we want to know how many free hours we
have
...
3. Interface
Without With
class Worker {
private $workedHours = 0;
public function work() {
$this->hours += 1;
}
public function getWorkedHours() {
return $this->workedHours;
}
}
class Company {
public function calculateHoursCapacity() {
$workers = $this->hrDepartment->getAllWorkers();
$freeHours = 0;
foreach ($workers as $worker) {
$freeHours += $this->hrDepartment->getFreeHoursLeft($worker);
}
return $freeHours;
}
}
class HRDepartmant {
private $hoursPerDay = 8;
public function getAllWorkers() {}
public function getFreeHoursLeft(Worker $worker) {
return $this->hoursPerDay - $worker->getWorkedHours();
}
}
class Worker implements WorkerInterface {
private $workedHours = 0;
public function work() {
$this->hours += 1;
}
public function getWorkedHours() {
return $this->workedHours;
}
}
class Company {
public function calculateHoursCapacity() {
$workers = $this->hrDepartment->getAllWorkers();
$freeHours = 0;
foreach ($workers as $worker) {
$freeHours += $this->hrDepartment->getFreeHoursLeft($worker);
}
return $freeHours;
}
}
class HRDepartmant {
private $hoursPerDay = 8;
public function getAllWorkers() {}
public function getFreeHoursLeft(WorkerInterface $worker) {
return $hoursPerDay - $worker->getWorkedHours();
}
}
4. Everything works perfectly
The programmer in the European department got
a $1000 bonus
The company can now handle the extra jobs.
5. There is one thing we can be sure of:
CHANGE
So be prepared!
6. New client requirement!
Our department in USA bought
new 24h workers - robots,
we need to handle that
The new functionality should be implemented by a programmer in Detroit
7. Interface
Without With
class Worker {
...
protected $maxHoursPerDay = 8;
…
public function getMaxHoursPerDay() {
return $this->maxHoursPerDay;
}
}
class Robot extends Worker {
protected $maxHoursPerDay = 24;
}
class HRDepartmant {
...
public function getFreeHoursLeft(Worker $worker) {
return $worker->getMaxHoursPerDay()
- $worker->getWorkedHours();
}
}
interface WorkerInterface {
...
public function getMaxHoursPerDay();
}
class Worker implements WorkerInterface {
...
private $maxHoursPerDay = 8;
…
public function getMaxHoursPerDay() {
return $this->maxHoursPerDay;
}
}
class Robot implements WorkerInterface {
private $workedHours = 0;
private $maxHoursPerDay = 24;
public function work() {
$this->hours += 1;
}
public function getWorkedHours() {
return $this->workedHours;
}
public function getMaxHoursPerDay() {
return $this->maxHoursPerDay;
}
}
class HRDepartmant {
...
public function getFreeHoursLeft(Worker $worker) {
return $worker->getMaxHoursPerDay() - $worker->getWorkedHours();
}
}
8. New client requirement!
Our departments in both Europe and USA
built canteens for their workers
The new functionality should be implemented by a programmer in Europe
9. Interface
Without With
class Worker {
...
private $meals = 0;
...
public function eat() {
$this->meals += 1;
}
...
public function getEatenMeals() {
return $this->meals;
}
}
class Company {
public function calculateMealsCost() {
$workers = $this->hrDepartment->getAllWorkers();
$mealsCost = 0;
foreach ($workers as $worker) {
$mealsCost += $this->accountantDepartment->getMealsCost($worker);
}
return $mealsCost;
}
}
class AccountingDepartment {
private $mealCost = 2; // $
public function getMealsCost(Worker $worker) {
return $worker->getEatenMeals() * $this->mealCost;
}
}
interface WorkerInterface {
public function eat();
public function getEatenMeals();
}
.. all the same code from the left side..
.. and after he started testing the application he saw something like this:
FatalErrorException in Robot.php line 21:
Error: Class ApplicationRobot contains 1 abstract method and must therefore
be declared abstract or implement the remaining methods
(ApplicationWorkerInterface::eat())
10. What is going on!?
The two programmers (one in Europe, one in USA) didn't have the same
knowledge about specific implementations of inherited classes.
Interface
Without With
In this case the programmer unintentionally gave the Robot class a human
behaviour in to form of the eat() method and after some manual testing he
deployed this code to production.
After a few months he was informed that the application is displaying some
strange behaviour: while the departmant in Detroit has only 150 employees,
450 meals are eaten every day.
In this case the programmer also unintentionally provided the Robot's class
with a human behaviour in the form of the eat() method, but he got a fatal error
exception.
He now has the knowledge of the Robot class and he knows that his
implementation is wrong — he can change it and make it more logical.
11. Interface
Without With
Pros:
● We'll build our application „faster”*
* yes, a couple of minutes less per class is really an irrefutable
fact
Cons:
● Sometimes we can give other classes
behaviours that shouldn't belong to them..
Pros:
● At some point in time, we will be able to save a
lot of our time, the client’s time, and of course
– money
● We can write tests for our classes in a better
way - i.e. FakeArrayRepositories,
FakeFileSystemManagers, FakeConnectors
(like Twitter / Facebook / Google) instead of
creating mocks everywhere..
Cons:
● We need 1-3 minutes to create an interface
12. Big picture of inheritance
Freelancer
Full time
worker
Worker
Intern
worker
...
Freelancer
... ...
Robots
Adding behaviour to a parent
class also unintentionally
provides child classes with this
new behaviour.
13. Big picture of interfaces
Freelancer
Full time
worker
Workable
interface
Intern
worker
Freelancer Robots
Eatable
interface
...
Introducing interfaces to our classes
made the application more robust.
Charge'able
interface
Inspection'able
interface
14. And what about the
Liskov substitution principle?
class Robot extends Worker implements WorkableInterface {
public function eat() {
throw new LogicException('Robots cannot eat');
}
}
but now in this following piece of code we need to do something like this:
foreach($workers as $worker) {
try {
$worker->eat();
} catch (Exception $e) {}
}
And this is a violation of the Liskov substitution principle..
because we cannot substitute Worker with Robot – we need to add
try/catch and we'll be duplicating these try-catches in multiple places..
… it is only a matter of time.
15. What about KISS
Do you remember the simpler and stupider class?
class A() {
public function doNothing() {
// yes this method really do nothing
}
}
There is another one - the simplest and stupidest
structure.. an interface!!
interface A() {
public function doNothing();
}
16. What about YAGNI
Yes, but YAGNI is about adding new
functionalities, and interface is a simple and
stupid API without implementation.
It's a simple contract that forces all the classes
that implement the interface to have the same
functionality... nothing more.. nothing less..
17. Favor composition over inheritance
Inheritance (whitebox reuse) and Composition (blackbox reuse)
If you try to reuse code by inheriting from a class
you will make the subclass dependent on the parent class.
This makes a system in many cases unnecessarily complex and less testable.