Leave a rating/review
Interfaces are contracts. They detail what an object can do. When we create an object, we are defining such an interface. Most of the time, that interface is for internal use only. That is, we have no intention for other objects to use it. But there are times when we want other objects to use it.
For instance, I may create an airport that will only accept objects that implement a flying interface. Every object must have a fly method.
This means my airport will accept planes and helicopters. Both can fly, but they do it in different ways.
It will also accept jet packs and rockets. Even birds can use my airport so long as they have a fly method.
Now we can use abstract classes to define our interface but that’s not always ideal.
An abstract class must be extended so every flyer would need to be a children. For this work, we’d nice to create a plane class and all children of the plane class could fly from the airport. Unfortunately, this means rockets, helicopters and bird would all have to be subclasses of the plane. That doesn’t really make sense.
We just want the interface so Dart provides a way for us to fulfill that contract without extending a class and that’s by using the implements keyword. By saying a class implements another class, we say that we are using the interface. This allows are object to be used wherever the other object was used.
By implementing the plane class on our car, we can now use the airport. This is exactly the same as abstract classes except we don’t set up an inheritance chain and we don’t inherit code. Notice, even when we implement an interface, we must annotate the methods included in the interface with the @override annotation. This lets us and the compiler now that we are fulling the interface contract.
To get started, open up DartPad.dev. We’re first going to define a repository that will perform simple read write operations. We’re not actually going to do anything processing - we’ll just print items for now.
First lets define our item class that the repository will use. We’ll just give it an id.
class Item {
int id;
Item(this.id);
@override
String toString() => 'item ${id.toString()}';
}
We also override toString to print it out. Now for our repository. It will contain a list of items and subsequent methods to operate on the list.
class Repository {
var items = <Item>[];
void update(Item item) {
print('update: $item');
}
void delete(Item item) {
print('delete: $item');
}
void add(Item item) {
print('adds: $item');
}
}
This repository is really useful. It’d be nice to create another repository although instead of fetching data off the hard drive, we’ll grab it from the network. Let’s create a new class. First let’s implement the repository.
class NetworkRepository implements Repository {
Now we have a network repo define. We have to override the interface of the regular repo. Again, we’re not going to write the actual repo code. Just print statements.
class NetworkRepository implements Repository {
@override
var items = <Item>[];
@override
void update(Item item) {
print('network update: $item');
}
@override
void delete(Item item) {
print('network delete: $item');
}
@override
void add(Item item) {
print('network adds: $item');
}
}
Now we have two different objects. Unique on their own but they share the same interface. Let’s put this to use. We’ll get started by creating a list that will only accept objects that use the repository interface.
var repos = <Repository>[];
Now we’ll add a couple of repositories.
repos.add(Repository());
repos.add(NetworkRepository());
We’ll create an item for dummy data.
var item = Item(1);
Finally, let’s loop through the repos and update the item.
for (var repo in repos) {
repo.update(item);
}
Now run the program. We iterate through both repos and update the item. Pretty awesome.