Including A Constructor In Your Interface
Today, while looking through a pull request containing PHP code, I stumbled over an issue that is mostly non-existent in other languages, so it might be difficult to find reliable information on best practices regarding this specific topic. I was, of course, delighted to see that the code in question included an interface and its related standard implementation, instead of a simple class. However, the interface included a constructor as well.
When you try to find out whether you should include a constructor within interfaces, you’ll probably end up realizing that this is not even possible in most other languages. PHP is very permissive in what it allows you to put into interfaces, sometimes to the point of making you consider choices you should not even be able to consider, to begin with.
So, while you might already have guessed from that last statement that I’m opposed to the idea of having constructors in an interface, even though PHP might allow it, I want to try to explain the reasoning behind my stance.
Contract About Interaction Between Objects
Object-oriented programming is about objects, and how they communicate with each other. An interface is a contract that fixes the details about how specific objects want to be interacted with. Through the interface, an object lets you know what vocabulary it understands. So, whoever the other party is, as long as it uses the correct vocabulary, it is welcome!
I’d like to quote a very important passage by Alan Kay from a mailing list discussion he had after being disappointed in what people generally perceived to be the most important point of object-oriented programming:
Just a gentle reminder that I took some pains at the last OOPSLA to try to remind everyone that Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea. The big idea is "messaging" -- that is what the kernal of Smalltalk/Squeak is all about [...]. The Japanese have a small word -- ma -- for "that which is in between" -- perhaps the nearest English equivalent is "interstitial". The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
So, the interface does only and exclusively care about the interaction points between objects. The instantiation (which is the constructor’s responsibility) is not about how objects interact, but rather about how to turn a class (blueprint) into an object in the first place. At the point where the constructor comes into play, you don’t even have an object yet.
Devoid Of Implementation Details
The constructor is used to put an object into an initialized state when it is instantiated. The current state of an object, and whether it is considered valid or not, is an implementation detail, and should therefore not be relevant for the interface.
The internal properties that define the state of an object should be inaccessible to outside objects. As the constructor should only deal with these internal properties, all of its work should therefore also be hidden from external objects as well.
If you get passed a View
object, and you want to render that view, you should not need to know whether that specific object had also asked for a Logger
when being instantiated.
As Lean As Possible
The Interface Segregation Principle (ISP), which is the I in SOLID, states that an interface should only contain the methods needed for one specific role. So, as an (extremely simplified) example, when creating an interface for a Validator
object, the interface probably should only contain the single method validate()
.
If the interface contains too many methods, this probably points to a design issue where your interface just mirrors your classes. This might lead to code where different responsibilities are so tightly coupled that you can’t make changes to one without affecting the other.
Consider Dependency Injection
When using dependency injection (and you really should), you usually pass in the dependencies of your object to be instantiated through its constructor. So, as an example, your console command might have an Input
dependency and an Output
dependency, so that it can receive input, parse it, and echo the corresponding output. This will yield a constructor along the lines of:
public function __construct(Input $input, Output $output);
Now, what if you now want to add logging to all of your console commands? You’ll need an additional dependency on a Logger
as well. This will change your constructor, though. To keep the option of replacing one implementation with another one, you need to be able to adapt the constructor so that it can accept whatever dependencies you will need. And adapting the constructor should not break your existing code.
Consider Multiple Implements
If you do have your interfaces properly segregated, like described above, you might want to have a class that implements several interfaces at once. As an example, you might want to have something like this:
class CachedRenderer implements Cacheable, Renderable { }
In such a case, if both interfaces would include a constructor, they would dictate incompatible signatures for your class. You would not be able to write a constructor that would work with both, except in the rare case where their constructor is identical.
Instantiation Needs Implementations Details
The goal of using interfaces is to make your code agnostic of specific implementations. Whenever you want to call a method declared in an interface, you can just rely on the object implementing the interface to accept that method call.
Instantiation is different, though. You cannot instantiate a new object without knowing some implementation details about the specific class to use. At some point, whether it is directly in one of your classes or factories, or whether it is within an IoC container, you will need to write a statement along the lines of:
$object = new \ExactNamespace\ExactClassToInstantiate( $arguments, $thatThe, $exactClass, $needs );
It is just not possible to instantiate a new object without knowing details about the implementation. That’s why the instantiation is usually put within factories and/or DI containers, you want to have one single place to make changes if you want to replace classes.
What About The Liskov Substitution Principle?
The Liskov Substitution Principle (LSP), which is the L in SOLID, states that wherever an object is used, you should be able to replace it with a subtype of that object without breaking the code. This means that your new object should be able to do everything that the original object could do or more.
So, what about the example we’ve given above where the subtype has a different constructor signature than the interface it implements. Won’t this break the LSP?
The LSP is not even concerned with instantiation at all. It does not state that you should be able to instantiate all classes in the same way. It only states that, when an object of a class is being used somewhere, it should be substitutable for an object of a subclass. Note the term “object” – we’re already dealing with instantiated objects at this point. The LSP is still intact, even if we have different constructors for different implementations.
What If I Want To Enforce A Specific Signature?
If you absolutely must provide a specific signature to be used as part of your API, you are already defining one part of the specific implementation, not just the contract.
In such a case, you should probably use an abstract class. Within the abstract class, you can provide as much implementation details as wanted or needed, while also marking up the parts that the extending class will need to take care of. So, you have a kind of implementation spectrum you can position your code on:
- Interface => no implementation specifics provided
- Abstract Class => some implementation specifics provided
- Class => all implementation specifics provided
Conclusion
Don’t put constructors into your interface definitions. They don’t serve a valid purpose, and they might cause all sorts of issues down the road.
Did I miss something obvious? Let me know in the comments below!
Everything you’ve covered in the article is great (I’m especially a fan of SOLID though I’m not as good at implementing it as I always want to be. Often, that has to do with deadlines, but that’s another discussion for another time :).
But this is what’s interesting to me:
I’ve never thought of doing so because interfaces, by definition, are meant to set up the contract between the classes which implement said interface.
I’ve always thought of interfaces as being responsible for defining the functionality/API/whatever that:
Must be implemented,
Will be exposed via the class.
In essence, it guarantees the implementation of a particular set of functions accessible to third-party clients. Period. (Is that too rigid?)
The idea of forcing a class to have a constructor beyond the one that’s provided by default has always seemed to be a class-level detail rather than an interface detail.
All that to say that I like your discussion around this because it shows how others might approach the problem and why they should/n’t define a constructor in the interface.Report
I have to agree that I too have never considered using a constructor in an interface.
However, I have gone through years of developing with languages where this was not even possible to do in the first place.
So I assume it depends a lot on your background on whether you would want to try to do this.
Also, I often see interfaces that are just the classes where the implementation has been ripped out. With such an approach, it is very easy to end up with a constructor in all the wrong places.
I actually had to think a bit about this stuff when commenting on the pull request (to come up with specific reasons, not just “Nope!”), and writing the article has made it even clearer in my head.
Also, reading your comment reminded me that not everyone is aware that “interface” is actually the “I” in “API” (Application Programming Interface). So you’re absolutely right that you want to define your API with your interfaces, and not any specific implementation details.
Thanks for the feedback!Report
Maybe it’s worth to mention that this rule also applies to other magic methods that deal with the instance’s life cycle states like
__destruct()
,__clone()
,__sleep()
and so on.ReportHey David,
Yes, excellent point!
Per definition, everything that is part of the instance’s lifecycle or its internal state should not be referenced within the interface. This is not necessarily tied to magic methods, but the ones you mention are definitely part of the bunch.
Thanks for helping fill the gaps!Report
Been an avid reader of your blog though i haven’t commented once.
Keep up the good work teaching WordPress/PHP developers OOP & best practices.
If you ever feel nobody reads your blog, this is a comment to let you know people do.Report
Hey Collins,
Thanks for the feedback, and for letting me know people read my stuff. Truly appreciated!
Cheers!Report
Very cleanly explains this. I was using constructors in my interfaces, but I started running into trouble when using DI and having objects depend on other classes.Report
Great article, thank you!
But what do you think about the
__toString()
magic method in the contract?For example, I have the
Location
contract. It defineslatitude()
andlongitude()
methods. But the implementation of it also has the__toString()
magic method, which is used in the code. I use the string converts for that object, and without it has been implemented – the code would be broken.One option is to define some not magic
toString()
method as a part of the contract. And call it in all of those places.But do I really need that? I like the ability to do things like:
I mean, yes I’m forcing all the implementations to have that
__toString()
public method. But I don’t care how they would implement that. Yes, it is “magic”, but the only difference betweentoString()
and__toString()
that the second is called for me automatically, which makes my code nicer.What do you think?Report
PS: Actually, now I have another idea. I think I’m missing another layer.
I have to define the abstract class Location, which will implement the Location contract.
Location contract will have only
latitude()
andlongitude()
methods.Abstract class will implement the contract, and provide the
__toString()
andfromString()
methods.Implementations of the Location contract would extend that abstract class.
By doing that I can have that “toString” logic in one place, on a base level.
I think that
fromString()
method helped me to understand that I miss the abstract class.But for many cases you would have only
__toString()
method required. For example, some simple value objects, like Language, which stores just the language code.But for me, that example illustrates that the
__toString()
method should NOT be the part of the contract. It is a good candidate for the abstract class. And it is the common place to do all the “to string”, “from string”, etc logic.Report