Joshua Hall
Some of the best reasons for using object-oriented programming (OOP) are its techniques for helping to keep our code clean and DRY. We’re going to explore some of Dart’s strategies for making your code reusable over separate classes.
Imagine we have two creature classes, each of them with their own set of behaviors. The obvious solution would be to just directly outline the methods for each class as we need them, but many classes aren’t going to be entirely unique and will share a lot of commonality with each other. We obviously want to find the most efficient structure for writing these classes in the most reusable way.
Our animals will have a few specific actions they can perform and a larger behavior composed out of them. As you can see, their diet may be different, but the methods are mostly the same and should ideally be broken out into something more reusable.
class Alligator {
void swim() => print('Swimming');
void bite() => print('Chomp');
void crawl() => print('Crawling');
void hunt() {
print('Alligator -------');
swim();
crawl();
bite();
print('Eat Fish');
}
}
class Crocodile {
void swim() => print('Swimming');
void bite() => print('Chomp');
void crawl() => print('Crawling');
void hunt() {
print('Crocodile -------');
swim();
crawl();
bite();
print('Eat Zebra');
}
}
main() {
Crocodile().hunt();
Alligator().hunt();
}
// Output should be
// Crocodile -------
// Swimming
// Crawling
// Chomp
// Eat Zebra
// Alligator -------
// Swimming
// Crawling
// Chomp
// Eat Fish
The most common option we have is extensions, where we can take the properties and methods on one class and make them available in another. Since we wouldn’t use Reptile
in its own instance, we can set it as an abstract class so it can’t be initialized, just extended.
abstract class Reptile {
void bite() => print('Chomp');
void swim() => print('Swimming');
void crawl() => print('Crawling');
void hunt() {
print('${this.runtimeType} -------');
swim();
crawl();
bite();
}
}
class Alligator extends Reptile {
// Alligator Specific stuff...
}
class Crocodile extends Reptile {
// Crocodile Specific stuff...
}
main() {
Crocodile().hunt('Zebra');
Alligator().hunt('Fish');
}
That’s nice for our current example, but as we added more animals it would quickly become evident that many of theses methods aren’t just for Reptiles. If we wanted to create a Fish
class with a swim method, instead of just extending Reptile
, which is very limiting when we need more functionality from other classes, since we can only use extends
one per class. We can use mixins to break our more universal behaviors into smaller, more reusable components that we can add to whatever class we need them in.
We just need to use the mixin
type to store our methods and use the with
keyword on every class we want it included in. Unlike extends, we can add as many mixins as we want to a class.
mixin Swim {
void swim() => print('Swimming');
}
mixin Bite {
void bite() => print('Chomp');
}
mixin Crawl {
void crawl() => print('Crawling');
}
abstract class Reptile with Swim, Crawl, Bite {
void hunt(food) {
print('${this.runtimeType} -------');
swim();
crawl();
bite();
print('Eat $food');
}
}
class Alligator extends Reptile {
// Alligator Specific stuff...
}
class Crocodile extends Reptile {
// Crocodile Specific stuff...
}
class Fish with Swim, Bite {
void feed() {
print('Fish --------');
swim();
bite();
}
}
main() {
Crocodile().hunt('Zebra');
Alligator().hunt('Fish');
Fish().feed();
}
The last trick we have is the ability to do something that I like to think about as a reverse-extension. We can create a mixin that utilizes the methods from a class, which we can then use with each subclass.
If we wanted to break Hunt
into its own mixin, we can use the on
keyword to tell it that it will only be used on the Reptile
class, which will give it access to all of its functionality, like our Swim
, Crawl
, and Bite
mixins.
This configuration should have the exact same output as our first one.
mixin Hunt on Reptile {
void hunt(food) {
print('${this.runtimeType} -------');
swim();
crawl();
bite();
print('Eat $food');
}
}
abstract class Reptile with Swim, Crawl, Bite {}
class Alligator extends Reptile with Hunt {
// Alligator Specific stuff...
}
class Crocodile extends Reptile with Hunt {
// Crocodile Specific stuff...
}
With the growing popularity of Flutter, it’s a good idea to get a good foundational understanding of the basics of the Dart programming language. Hopefully this was a helpful introduction into clarifying some of the mysteries around reusing Dart classes.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Thank you so much, It was very helpful and easy to understand.