7 Extension Methods¶
Up to this point in the book, you’ve been writing your own classes and methods. Often, though, you use other people’s classes when you’re programming. Those classes may be part of a core Dart library, or they may be from packages on Pub. In either case, you don’t have the ability to modify them at will.
However, Dart has a feature called extension methods that allows you to add functionality to existing classes. Even though they’re called extension methods, you can also add other members like getters, setters or even operators.
Extension Syntax¶
To make an extension, you use the following syntax:
extension on SomeClass {
// your custom code
}
This should be located at the top level in a file, that is, not inside another class or function. Replace SomeClass
with whatever class you want to add extra functionality to.
You may give the extension itself a name if you like. In that case, the syntax is as follows:
extension YourExtensionName on SomeClass {
// your custom code
}
You can use whatever name you like in place of YourExtensionName
. The name is only used to show or hide the extension when importing it in another library.
Have a look at a few of the following examples to see how extension methods work.
String Extension Example¶
Did you ever make secret codes when you were a kid, like a=1
, b=2
, c=3
, and so on? For this example, you’re going to make an extension that will convert a string into a secret coded message. Then you’ll add another extension method to decode it.
In this secret code, each letter will be bumped up to the next one. So a will be b, b will be c, and so on. To accomplish that, you’ll increase the Unicode value of each code point in the input string by 1
. If the original message were “abc”, the encoded message should be “bcd”.
Solving in the Normal Way¶
First, solve the problem as you would with a normal function. Add the following to your project:
String encode(String input) {
final output = StringBuffer();
for (final codePoint in input.runes) {
output.writeCharCode(codePoint + 1);
}
return output.toString();
}
You loop through each Unicode code point and increment it by 1
before writing it to output
. Finally, you convert the StringBuffer
back to a regular String
and return it.
Test your code out by writing the code below in main
:
final original = 'abc';
final secret = encode(original);
print(secret);
Run that and you’ll see the result is bcd
. It works!
Converting to an Extension¶
The next step is to convert the encode
function above to an extension so that you can use it like so:
final secret = 'abc'.encoded;
Since this extension won’t mutate the original string itself, a naming convention is to use an adjective rather than a commanding verb. That’s the reason for choosing encoded
, rather than encode
, for the extension name.
Like classes, extensions can’t be located inside of a function. So add the following code somewhere outside of main
:
extension on String {
String get encoded {
final output = StringBuffer();
for (final codePoint in runes) {
output.writeCharCode(codePoint + 1);
}
return output.toString();
}
}
Look at what’s changed here from its previous form as a function:
- The keywords
extension on
are what make this an extension. You can add whatever you want inside the body. It’s as ifString
were your own class now. - Rather than making a normal method, you can use a getter method. This makes it so that you can call the extension using
encoded
, without the parentheses, rather thanencoded()
. - Since you’re inside
String
already, there’s no need to passinput
as an argument. If you need a reference to the string object, you can use thethis
keyword. Thus, instead ofinput.runes
, you could writethis.runes
. However,this
is unnecessary and you can directly accessrunes
. Remember thatrunes
is a member ofString
and you’re insideString
.
Check that the extension works:
final secret = 'abc'.encoded;
print(secret);
You should see bcd
as the output. Nice! It still works.
Adding a Decode Extension¶
Add the decoded
method inside the body of the String
extension as well:
String get decoded {
final output = StringBuffer();
for (final codePoint in runes) {
output.writeCharCode(codePoint - 1);
}
return output.toString();
}
If you compare this to the encoded
method, though, there’s a lot of code duplication. Whenever you see code duplication, you should think about how to make it DRY.
Refactoring to Remove Code Duplication¶
Refactor your String
extension by replacing the entire extension with the following:
extension on String {
String get encoded => _code(1);
String get decoded => _code(-1);
String _code(int step) {
final output = StringBuffer();
for (final codePoint in runes) {
output.writeCharCode(codePoint + step);
}
return output.toString();
}
}
Now the private _code
method factors out all of the common parts of encoded
and decoded
. That’s better.
Testing the Results¶
To make sure that everything works, test both methods like so:
final original = 'I like extensions!';
final secret = original.encoded;
final revealed = secret.decoded;
print(secret);
print(revealed);
This will display the following encoded and decoded messages:
J!mjlf!fyufotjpot"
I like extensions!
Great! Now you can amuse your friends by giving them encoded messages. They’re actually a lot of fun to solve.
Int Extension Example¶
Here’s an example of an extension on int
.
extension on int {
int get cubed {
return this * this * this;
}
}
Notice the use of this
to get a reference to the int
object, which will be 5
in the example below.
You use the extension like so:
print(5.cubed);
The answer is 125
.
As you can see, you can do a lot with extensions. Although they can be very powerful, extensions by definition add non-standard behavior, and this can make it harder for other developers to understand your code. Use extensions when they make sense, but try not to overuse them.
Oh, one more thing.
Uif!tfdsfu!up!mfbsojoh!Ebsu!xfmm!jt!up!dg"ewtkqwu"cpf"lwuv"vt{"vjkpiu0"Vlqfh#|rx*uh#uhdglqj#wklv/#wkdw#reylrxvo|#ghvfulehv#|rx1#Kssh$nsf%
Challenges¶
Before moving on, here’s a challenge to test your knowledge of extension methods. It’s best if you try to solve it yourself, but a solution is available with the supplementary materials for this book if you get stuck.
Challenge 1: Time to Code¶
Dart has a Duration
class for expressing lengths of time. Make an extension on int
so that you can express a duration like so:
final timeRemaining = 3.minutes;
Key Points¶
- Extension methods allow you to give additional functionality to classes that are not your own.
- Use extensions when they make sense, but try not to overuse them.