Why you should never extend the interface
Hello, there
What's wrong with this code sample?
<?php
class MyClass {
public function myFunction() {
}
}
What's wrong with this code sample?
<?php
class MyClass {
public function myFunction() {
}
}
class myOtherClass extends MyClass {
public function MyOtherFunction() {
}
}
class Controller {
public function doSomething(MyClass $object) {
$object->MyOtherFunction();
}
}
On first blush, it might seem that this is ordinary inheritance, and we're doing everything we should be doing. But there's something very wrong here.
The problem here is that we're extending the interface. Extending the interface itself isn't necessarily bad, but we're making a second mistake: we're then typehinting on the wrong object type.
Let's talk about why we want to avoid this practice.
The Liskov Substitution Principle
I've discussed the Liskov Substitution Principle a few times through this newsletter. But let's go over it again.
The Liskov Substitution Principle says that one object should be replacable with another object of the same type, without breaking the program.
In other words, all objects of type A should replace one another, and the application should work just fine.
But our code sample above has a big problem: we're typehinting on one object type (MyClass) but we're relying on the interface of a different object type: myOtherClass. This means that if we actually pass in an instance of MyClass, our application will break.
Let's fix it.
So, now that we know what the problem is, how do we fix it? There are three different possible solutions.
1. Treat as abstract. First, we can treat MyClass as an abstract class, and mark it abstract. We can then include the abstract method definition, but not the code. This fixes our typehint.
<?php
abstract class MyClass {
public function myFunction() {
}
public function MyOtherFunction() {
}
}
class Controller {
public function doSomething(MyClass $object) {
$object->MyOtherFunction();
}
}
On first blush, it might seem that this is ordinary inheritance, and we're doing everything we should be doing. But there's something very wrong here.
The problem here is that we're extending the interface. Extending the interface itself isn't necessarily bad, but we're making a second mistake: we're then typehinting on the wrong object type.
Let's talk about why we want to avoid this practice.
The Liskov Substitution Principle
I've discussed the Liskov Substitution Principle a few times through this newsletter. But let's go over it again.
The Liskov Substitution Principle says that one object should be replacable with another object of the same type, without breaking the program.
In other words, all objects of type A should replace one another, and the application should work just fine.
But our code sample above has a big problem: we're typehinting on one object type (MyClass) but we're relying on the interface of a different object type: myOtherClass. This means that if we actually pass in an instance of MyClass, our application will break.
Let's fix it.
So, now that we know what the problem is, how do we fix it? There are three different possible solutions.
1. Treat as abstract. First, we can treat MyClass as an abstract class, and mark it abstract. We can then include the abstract method definition, but not the code. This fixes our typehint.
<?php
abstract class MyClass {
public function myFunction() {
}
abstract public function MyOtherFunction();
}
Once we've done this, our typehint is accurate and we don't have to worry about the method we want not existing, because the abstract definition guarantees it.
2. Change the typehint for the object being used. Instead of fixing the base class, we can fix the typehint and typehint on the actual object type we want. This solves the problem by ensuring that we are telling the application precisely what to expect.
<?php
class Controller {
public function doSomething(MyOtherClass $object) {
$object->MyOtherFunction();
}
}
Of course, we are now hinting on a specific object, instead of an interface. But this is still better than relying on an interface that may or may not exist in future.
3. Define different interfaces, and hint on the one we want. It's possible in PHP to define two interfaces, and implement both of them in the same object. For example:
<?php
interface MyClass {
public function myFunction(); }
}
Once we've done this, our typehint is accurate and we don't have to worry about the method we want not existing, because the abstract definition guarantees it.
2. Change the typehint for the object being used. Instead of fixing the base class, we can fix the typehint and typehint on the actual object type we want. This solves the problem by ensuring that we are telling the application precisely what to expect.
<?php
class Controller {
public function doSomething(MyOtherClass $object) {
$object->MyOtherFunction();
}
}
Of course, we are now hinting on a specific object, instead of an interface. But this is still better than relying on an interface that may or may not exist in future.
3. Define different interfaces, and hint on the one we want. It's possible in PHP to define two interfaces, and implement both of them in the same object. For example:
<?php
interface MyClass {
public function myFunction(); }
interface myOtherClass extends MyClass {
public function MyOtherFunction();
}
With these two interfaces, we can typehint on the interface we want, but leave the implementation details up to the future object that's going to be created. We're still guaranteed a particular interface, and this makes it easy to follow the Liskov Substitution Principle.
Objects know one another by their interface.
Regardless of the solution you might choose, there's one rule that you have to remember and understand: objects know each other by their interfaces.
The public methods form the "interface" or "API" that other objects use to communicate with a given object. Outside objects know nothing of the internal protected and private methods an object has; they can't use them. So, an object's interface is the only way to describe it to the outside world.
This interface therefore define's an objects type. In PHP, interfaces can't define anything besides public methods, and this is by design: when we typehint, we're saying "give me an object that has these methods."
public function MyOtherFunction();
}
With these two interfaces, we can typehint on the interface we want, but leave the implementation details up to the future object that's going to be created. We're still guaranteed a particular interface, and this makes it easy to follow the Liskov Substitution Principle.
Objects know one another by their interface.
Regardless of the solution you might choose, there's one rule that you have to remember and understand: objects know each other by their interfaces.
The public methods form the "interface" or "API" that other objects use to communicate with a given object. Outside objects know nothing of the internal protected and private methods an object has; they can't use them. So, an object's interface is the only way to describe it to the outside world.
This interface therefore define's an objects type. In PHP, interfaces can't define anything besides public methods, and this is by design: when we typehint, we're saying "give me an object that has these methods."
Comments
Post a Comment