2

With C#8 Microsoft introduced Default Implementation for interface methods. It's still a fairly new feature and there seem to be many concerned bloggers writing about this.

What I'm wondering is if Default Implementation has the potential to be a helpful tool for Dependency Inversion and DI or if it promotes a bad programming style? Does it break any of the well known principles like SOLID?

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Josh
  • 287
  • 1
  • 8
  • In what way would you think this would help with dependency inversion? – Luaan Nov 26 '19 at 08:09
  • Well, with dependency inversion you'll end up with many interfaces that have one implementation. And I was wondering if you could avoid this with a default implementation ... – Josh Nov 26 '19 at 08:13
  • 1
    Oh boy, no, that's not the point at all. You still can't create an instance of an interface - you still need a class for that. It has no use whatsoever in the scenario of "interface with just one implementation". – Luaan Nov 26 '19 at 08:15
  • As for SOLID, Java 8 introduced default members *5 years ago* and people have been arguing about SOLID ever since. SOLID is a set of principles meant for developers, not language constructs. *Intefaces* can violate SOLID if used improperly, and so do DIMs. – Panagiotis Kanavos Nov 26 '19 at 12:39
  • *Properly* used though they make it easier to follow SOLID, by *extracting* traits/mixins/aspects into separate constructs. You no longer need to have numbered interfaces to implement versioning either. You don't have to *open* clients to implement new methods – Panagiotis Kanavos Nov 26 '19 at 12:43

1 Answers1

6

There's two main design goals of default interface implementation. The more important goes all the way back to guidelines about designing interfaces. In particular, as soon as you publish an interface, it should be set in stone and never ever change. The problem is, this is also a rule that's ignored about... all of the time.

The first and main utility is that default interface implementation allows you to introduce new members into an interface, without breaking source or binary compatibility with consumers of that (public) interface. This still limits the kind of changes you can do when changing a public interface, but also makes it easier for clients to use the new interface - upgrading is free, and they can start using the new features right away.

The second design goal is a way to extend classes with traits - this has long been used in game development. The basic idea is that you can add new well-defined behaviours to a class just by having it implement an interface, while also retaining the ability to modify the behaviour in the class itself. This is essentially a relatively weak form of meta-programming.

Of course, just because those were the design goals doesn't mean that they are the only way you should use default implementations. But if you generalize a bit, you get these two basic uses:

  • Extending interfaces without breaking compatibility and having to version interfaces.
  • Extending classes without having to create a rigid tree hierarchy that you get from class inheritance.

In fact, you could even argue that this is both simpler, clearer and more powerful than class inheritance. In a way, this is a continuation of the approach started with extension methods - in essence, default interface method implementation is an extension method which is also virtual. The default implementation can only work with the public interface, but an implementing class can also use its own hidden state. It gives C# a limited form of multiple inheritance, without having to deal with how the state of two "parents" is joined together (since interfaces don't have any state).

Finally, if you're worried about principles like SOLID, let's give that a go:

  • SRP - No real change, a few new options of non-destructively adding new logic in one place. I say this is a slight win for SRP.
  • OCP - You get powerful new ways of extending classes without having to modify the class itself, as long as that class implements an interface where the new feature makes sense. But you can't change the existing classes behaviour this way, so it's still closed for modification. Pretty useful win here.
  • LSP - Not really affected. Some minor victories from allowing you to add new substitutable behaviour to a class just through implementing an interface (with the option to override that behaviour as needed).
  • ISP - This can go both ways. Default implementations may make it easier to have a lot of small interfaces if you have reasonable default implementations. But they can also encourage you to keep modifying existing interfaces instead of adding new ones.
  • DIP - Mostly unaffected. You can more easily add new abstractions if they are reasonable extensions to what you already have, or if a default behaviour can be implemented without relying on state. You can also be tempted to use the default behaviour as contractual, but IMO it's still less of a temptation than in an abstract method (where you also have control over the state).
Luaan
  • 62,244
  • 7
  • 97
  • 116