DMarlon Studio

Lesson 5 · 5 of 20

Abstract classes and interfaces · Dart

Define contracts in Dart with abstract classes and interfaces, and see the difference between extends and implements with several payment methods.

Dart · Abstract classes and interfaces in DMarlon Studio: the example's code and its output in the terminal

An `abstract` class cannot be instantiated (PaymentMethod() does not compile) and may declare members without a body: name and fee are abstract, so each subclass decides what they are. charge, on the other hand, is concrete: it is written once and every subclass inherits it, even though it calls fee, which the base class cannot work out yet.

Card and BankTransfer inherit with extends and fill in the gaps. The card charges 1.5 % plus 25 cents, using integer division (~/); the transfer charges no fee. Notice that name can be implemented with a getter (get name =>) or with a final field: callers cannot tell the difference.

`implements` is something else: it takes only the contract and inherits no code. Refundable is declared as an abstract interface class, a Dart 3 modifier saying it is meant to be implemented. On top of that, in Dart every class is an implicit interface: GiftCard implements PaymentMethod, so it has to write every member itself, charge included, since it inherits nothing.

In the loop the variable has type PaymentMethod, and is Refundable asks whether the object also fulfils the other contract. Because Refundable is not a subtype of PaymentMethod, Dart does not change the variable's type after the is, so it has to be cast with as Refundable before calling refund. The transfer is not refundable, which is why it prints no such line.

Code · main.dart

// Abstract: cannot be instantiated, may leave methods without a body.abstract class PaymentMethod {  String get name;  int fee(int amount); // abstract: each subclass decides  // Concrete method that every subclass inherits.  String charge(int amount) => '$name: ${amount + fee(amount)} (fee ${fee(amount)})';}// A pure contract: whoever implements it must write every member.abstract interface class Refundable { String refund(int amount); }class Card extends PaymentMethod implements Refundable {  @override String get name => 'Card';  @override int fee(int amount) => amount * 15 ~/ 1000 + 25; // 1.5 % + 25  @override String refund(int amount) => 'refunds $amount to the card';}class BankTransfer extends PaymentMethod {  @override final String name = 'Transfer';  @override int fee(int amount) => 0;}// implements takes only the contract: nothing is inherited.class GiftCard implements PaymentMethod, Refundable {  @override String get name => 'Gift card';  @override int fee(int amount) => 0;  @override String charge(int amount) => '$name: $amount from the balance';  @override String refund(int amount) => 'adds $amount back to the gift card';}void main() {  for (final PaymentMethod method in [Card(), BankTransfer(), GiftCard()]) {    print(method.charge(4000));    if (method is Refundable) print('  ${(method as Refundable).refund(4000)}');  }}

Output · dart run main.dart

Card: 4085 (fee 85)
  refunds 4000 to the card
Transfer: 4000 (fee 0)
Gift card: 4000 from the balance
  adds 4000 back to the gift card

Key points

  • An abstract class cannot be instantiated and may leave methods without a body.
  • extends inherits code; implements only requires the contract.
  • Every Dart class can also be used as an interface.
  • A class can implement several interfaces at once.
  • is checks whether an object fulfils an interface.

Exercise

Create a MobileWallet payment method that implements PaymentMethod and Refundable, with a flat 10-cent fee, and add it to the list.