We'll take a look at two more Dart features that are useful in some situations but aren't crucial when using Dart, and you'll probably not use them on a daily basis.
Dart lets us overload its default behavior when using standard operators such as ==
, +
, -
, []
, or []=
. A typical use case is when using 2D/3D vectors:
class Vector { int x, y; Vector(this.x, this.y); operator ==(Vector v) => this.x == v.x && this.y == v.y; operator +(Vector v) => new Vector(this.x + v.x, this.y + v.y); operator -(Vector v) => new Vector(this.x - v.x, this.y - v.y); }
We can use unit testing to check whether operators work as expected:
var v1 = new Vector(5, 3); var v2 = new Vector(7, 2); Vector v3 = v1 + v2; expect(v3.x, 12); expect(v3.y, 5); expect((v1 - v2) == new Vector(-2, 1), isTrue); var v6 = new Vector(3, 5); expect(v1 == v6, isFalse);
Unlike JavaScript, there's no ===
operator (three equal signs) in Dart that compares variables for having the same type and value. The way Dart actually compares objects is up to you by overloading their ==
operator. To check whether two object reference the same instance, you can use the top-level identity()
function.
As another use case, we could extend the book shelf example from previous chapters with overloaded "array subscription" operators, []
and []=
:
class Shelf { Map<String, Book> books = {}; add(Book book) => books[book.title] = book; operator []=(String key, Book book) => books[key] = book; operator [](Book book) => books[book.title]; } class Book { String title; Book(this.title); }
This lets us use Shelf
objects just like maps, where Book
instances are stored with their names as keys:
var shelf = new Shelf(); var b1 = new Book('Book 1'), var b2 = new Book('Book 2'), shelf.add(b1); shelf[b2.title] = b2; expect(shelf[b1], same(b1));
Well, we could just call shelf.books[b2.title] = b2
, but you get the idea how []=
operator works.
There are quite a lot of operators to overload and we only used the most common ones here, but take a look at all of them at https://www.dartlang.org/docs/dart-up-and-running/ch02.html#overridable-operators.
In Chapter 1, Getting Started with Dart, we mentioned that Dart has a single inheritance model with mixins. The proper definition of mixins is rather complicated (refer to the official definition for Dart at https://www.dartlang.org/articles/mixins/) but simply put, a mixin is a common behavior/feature shared among multiple unrelated classes. In practice, this means a set of methods and properties that are common to all classes implementing this mixin.
We can extend our book shelf example once more by adding a mixin, defining that each book and shelf can be described by keywords that can be arranged into genres:
class Genre { List<String> keywords = []; guessGenre() { // Try to guess what's the overall genre for this object. // Implementation is not interesting for us here. } } class Shelf extends Object with Genre { /* Remains unchanged. */ } class Book extends Object with Genre { /* Remains unchanged. */ }
Mixins are implemented by the with
keyword. Each class implementing mixins has to specify its parent class even when it's the default Object
class. Both Shelf
and Book
classes now have the keywords
property and a guessGenre()
method.
The Genre
mixin is just a class that has to follow three requirements:
Object
classsuper
We can test both classes whether they properly implement the Genre
mixin. Note that one class can implement multiple mixins at the same time:
// Objects created as above. expect(shelf, new isInstanceOf<Genre>()); expect(b1, new isInstanceOf<Genre>()); expect(shelf.keywords, new isInstanceOf<List>()); expect(b1.keywords, new isInstanceOf<List>()); expect(shelf.guessGenre, new isInstanceOf<Function>()); expect(b1.guessGenre, new isInstanceOf<Function>());
When any of these tests fail, the code throws an exception, so outside unit tests, we could check whether an object implements a mixin rather with the is
keyword:
if (shelf is Genre) { // Object shelf of class Shelf implements Genre mixin. }
3.129.70.185