跳转至

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 if String 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 than encoded().
  • Since you’re inside String already, there’s no need to pass input as an argument. If you need a reference to the string object, you can use the thiskeyword. Thus, instead of input.runes, you could write this.runes. However, this is unnecessary and you can directly access runes. Remember that runesis a member of String and you’re inside String.

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 intso 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.