Generally, constructors in an interface are considered as being an anti-pattern. Some languages even don't allow that.

Then I don't understand how we're supposed to deal with polymorphic immutable objects in the some particular IOC contexts...

Say we have to instantiate a polymorphic object, its class implementing our interface is implemented and supplied by the client.
As a dumb but simple scenario let's say this object is a value object and as such should be immutable, which mean the object's state should be valid from the moment it's instantiated…

Our code:

// A user class implementing our interface...
$immutablePolymorphe = $userConfig['immutable_polymorphe_class'];

// Here we must know what to inject to its constructor...
$immutablePolymorphe = new $immutablePolymorphe($state);

// Then do something with that polymorphe...

So don't we have to constrain the constructor here, hence the interface? Am I missing something?

  • Could you perhaps clarify your IOC example with some pseudocode? I've written a preliminary answer but would like to expand on that point once I understand what you meant with that. – amon Nov 29 '18 at 21:34
  • @amon, see my question. Sorry for the big changes but it is much more accurate now. – user285001 Nov 29 '18 at 22:34
  • 1
    See my updated answer. If I understand correctly this might be solved by using factory functions, rather than by using the concrete class directly. That way, the constructor doesn't have to be part of the interface. I don't think this has anything to do with immutable vs. mutable objects. – amon Nov 29 '18 at 23:06
  • @amon, you say: "I don't think this has anything to do with immutable vs. mutable objects". Indeed, stricto-sensu you're right. I just want to emphasize object immutability because unlike a mutable object it has necessarily an implemented constructor. – user285001 Nov 30 '18 at 0:11

In an OOP context, having a constructor in an interface makes zero sense: The concrete type – which would be needed to construct an instance of a type – isn't known to the consumers of an interface. All a consumer knows is that a given object implements that interface and can be sent various messages.

However, not all programming is object-oriented. When using techniques such as generics or templates, we are using interfaces/typclasses/traits/concepts not to constrain objects, but to constrain types. Requiring that a type then offers a particular constructor makes perfect sense. In fact, some languages (such as Rust or Haskell) do not have a similar concept of a “constructor”, but only ordinary functions that happen to return an instance of that type – and allow such non-instance functions to be part of an interface.

It is usually not possible to write an interface that requires an object to be immutable. While an interface might not offer any mutators, it could provide additional methods that do change its state. I'm therefore not sure whether your IOC example could be realized in a typical language.

Regarding the dependency injection example in the question, a typical solution is to not inject a class with its constructor, but to inject an arbitrary factory function that is able to construct a suitable object. This factory function can be given lots of possible dependencies but does not have to use them, thus allowing us to abstract over the specific signature of the real constructor. For example:

function make_A($dep_a, $dep_b) {
  return new ImplementationA($dep_a);
function make_B($dep_a, $dep_b) {
  return new ImplementationB($dep_a, $dep_b);

// in some configuration:
$DI_CONFIG['SomeInterface'] = make_A;  // or make_B

// in the DI container:
$dep_a = ...;  // resolve dependency
$dep_b = ...;
$some_instance = $DI_CONFIG['SomeInterface']($dep_a, $dep_b)
  • I don't understand the difference between our examples? What makes it different for your DI container? It seems to me that instead of having to know what to inject to the constructor, it has to know what to inject to the closure, which is still supplied by... (the client?)… It could be not 1 or 2 but more arguments of various types... Don't we still have the same problem? Don't we have to constrain the constructor with its parameters types here, hence the interface? – user285001 Nov 30 '18 at 0:18
  • @user285001 My point there was really minor, but it might have been worth mentioning: instead of only abstracting over the concrete type that implements some interface, we can also abstract over the signature of the constructor. For example, by having our DI container use factory functions instead of classes. This is arguably an application of the adapter pattern: this factory function can adapt the original constructor to whatever interface is expected by the DI container. – amon Dec 1 '18 at 21:01
  • +1 Looking at these closures as some kind of adapters I see the point now (sorry, I'm slow). I've never seen closures and adapter pattern combined and employed that way, especially the adapter being passed by the client... that's interesting. I accept your answer while no more pertinent answers are supplied. Thanks for your time. – user285001 Dec 2 '18 at 10:44

The interface may not know about the constructor, but the concrete implementing class does, and can create new immutable objects as necessary. Consider an interface like (in Scala):

trait Immutable {
    def increment: Immutable

Here, increment returns another Immutable object. You can implement it like this:

class Count(count: Int) extends Immutable {
    def increment: Immutable = new Count(count + 1)

Count knows about its own constructor, so even though the constructor isn't part of the interface, it can still be used to implement parts of the interface. Likewise, a factory class would know internally about all the constructors for concrete classes it can construct.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.