Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use interfaces in Dart?

I was reading Dart's documentation and I was a little bit confused, maybe because I'm coming from Ruby, as to how to use interfaces. Of course, interfaces are not unique to Dart and there are quite a number of explanations out there on when one should use an interface. This one, for example, seems to be saying that interfaces are only useful when you're in a team. What is it even supposed to mean in the open source world, where everybody reads and reuses somebody else's code?

One interesting explanation I've seen seemed to be implying that interfaces are used:

  1. in languages that lack multiple inheritance, and
  2. for that matter they somehow serve as a workaround for the absence of multiple inheritance.

I don't understand that. I understand that modules in Ruby are a workaround because they allow me to define real methods with actual bodies. Interfaces only allow me to define what methods a class implementing it should have. What's the catch? Can anyone tell of a real useful example where I can immediately see the value of using interfaces?

P.S. On a related note, is there a way to use multiple inheritance in Dart?

like image 212
snitko Avatar asked May 08 '12 16:05

snitko


3 Answers

Update: the interface keyword has since been removed from Dart.


Interfaces are useful because they allow you to switch implementations of a class, whilst still allowing validation that the type being passed in meets the requirements of the interface.

Take the following (often used) example:

interface Quackable {
  void quack();
}

This defines the requirements of a class that will be passed to a method such as:

sayQuack(Quackable quackable) {
   quackable.quack();
}

which allows you to make use of any implementation of a Quackable object, such as:

class MockDuck implements Quackable {
  void quack() => print("quack");
}

class EnterpriseDuck implements Quackable {
  void quack() {
    // connect to three enterprise "ponds"
    // and eat some server bread
    // and say "quack" using an messaging system
  }

}

Both of these implementations will work with the sayQuack() function, but one requires significantly less infrastructure than the other.

sayQuack(new EnterpriseDuck());
sayQuack(new MockDuck());

I use this pattern all the time in the Java world, when building solutions that make use of some "enterprise duck". When developing locally, all I simply need is to be able to call the sayQuack() function and return some hard-coded, mock data.

Duck typing

Because Dart is optionally typed, you don't actually need to use the interface, simply writing a class that contains the correct method signature will work (although the tools won't be able to validate it).

class Person {   // note: no implements keyword
  void quack() => "I'm not a duck";
}

sayQuack(new Person()); // provides the quack method, so this will still work

All classes are interfaces

Finally, all Classes are also interfaces. This means that even though a third party system may have been written without using interfaces, you can still use a concrete class as though it were an interface.

For example, imagine the following enterprise library:

class EnterpriseDuck { // note: no implements keyword
  void quack() {
    // snip
  }
}

sayQuack(EnterpriseDuck duck) {  // takes an instance of the EnterpriseDuck class
  duck.quack();
}

And you want to pass a mock duck into the sayQuack method in a way that the type checker can validate. You can create your mockDuck to implement the interface implied by EnterpriseDuck, simply by using the EnterpriseDuck as an interface:

class MockDuck implements EnterpriseDuck {
  void quack() => "I'm a mock enterprise duck";
}

Multiple Inheritance

In terms of multiple inheritance, this is not possible in Dart. You can, however, implement multiple interfaces and provide your own implementations of the required methods, eg:

class MultiDuck implements Quackable, EnterpriseDuck, Swimable {
  // snip...
}

Interfaces can have default classes

As you use Dart, you will find that most "classes" are actually interfaces. List, String etc... are all interfaces with default implementations provided. When you call

List myList = new List();

you are actually using a List interface, and the new keyword redirects from the interface to an underlying default List implementation.

With regards to developing in a team

Interfaces are useful in team development, even in the open source world. The interface defines the methods and properties that you should be building so that your component works with my component. You can build your own test implementation of that interface, and I can build my concrete implementation of that interface, and when we're both done, we can integrate. Without the published, shared interface, I would need to provide my concrete implementation before you could really get started.

Hope that helps!

like image 167
Chris Buckett Avatar answered Nov 19 '22 07:11

Chris Buckett


Basically, interfaces have nothing to do with multiple inheritance. It is possible to fake multiple inheritance and abuse interfaces when doing it, but if you want true multiple inheritance (or mixins or traits), then Dart doesn't provide them (currently -- I believe that mixins will find their way in some day).

What are interfaces good for is explicit contract. Say that you have two components A and B that need to work with each other. You surely can call B from A directly and it will work, but the next time you will want to change B, you will have to look at A how it is using it. That's because B didn't expose an explicit interface. Yes, the right word for interfaces isn't implement but expose.

If you hide the implementation of B behind an interface and only provide that interface to A, then you can change B at will and only worry about still exposing the same interface. The interface can even be exposed by more than one class, and the caller doesn't have to care (or even know).

Note that the word interface has two meanings here: the general contract of the component, which can also be described in plain english in the documentation, and a special language construct that helps you describing (and also enforcing) some parts of the contract right inside the programming language.

You don't necessarily have to use the language construct, but it is considered good style to use it to describe those parts of the contract the programming language allows you to.

Now, what the heck is a contract here? Simply put, contract is a description of what the component expects from its user and what the user can expect from the component.

For example, let's say I have a method that computes absolute value of a number:

class MathUtils {
  /// Computes absolute value of given [number], which must be a [num].
  /// Return value is also a [num], which is never negative.
  absoluteValue(number) {
    ... here's the implementation ...
  }
}

The contract here is completely described in the documentation comment (well, not completely, we could also describe what absolute value is, but this is good enough). Well... but some parts of the comment can be expressed directly in the language, right?

class MathUtils {
  /// Computes absolute value of given [number].
  /// Return value is never negative.
  num absoluteValue(num number) {
    ... here's the implementation ...
  }
}

Note that some parts of the contract simply can't be expressed in the programming language -- here, the language has no idea what absolute value is, this needs to stay in the comment. Also, you can't express that the return value is never negative, so this has to stay in the comment too. But in fact, readers of your code know what an absolute value is (and that it is never negative) and the method name is pretty clear about the purpose, so the comment can be completely left out:

class MathUtils {
  num absoluteValue(num number) {
    ... here's the implementation ...
  }
}

So now, some parts of the contract are expressed explicitly, using the language means, and some parts are expressed implicitly (you rely on people knowing what absolute value is).

And interfaces in the language are used to decouple the contract from implementation. It is probably overkill to use in small programs, but it pays off when doing larger programs (which doesn't have to involve team work).

Uff, this turned out to be longer than I expected. Hope that helps.

like image 34
Ladicek Avatar answered Nov 19 '22 06:11

Ladicek


Interfaces are part of the type system in Dart and type declarations are optional. This means that interfaces are optional as well.

Interfaces help you document the methods that your object responds to. If you implement the interface then you promise to implement all the methods in the interface.

Dart uses this information to present compile-time warnings of type mismatches, to provide more helpful suggests for code assist and to assist with some refactorings.

like image 1
David Buck Avatar answered Nov 19 '22 07:11

David Buck