The use of abstract classes

Dart abstract classes can declare only methods or have concrete method implementations, but cannot be directly used to create instances. They can only be inherited or used as interfaces.

Define an abstract class Animal

abstract class Animal {
  // Declare only the eat method
  void eat();

  // Declare a method with an implementation
  void sleep() {
    print("Sleep"); }}Copy the code

Continued use

class Cat extends Animal {
  @override
  void eat() {
    print("Meow meow eat.");
    sleep();
  }

  // The sleep method is not implemented
}
Copy the code

ACTS as an interface

class Cat implements Animal {
  void eat() {
    print("Eat");
  }

  // The sleep method must be implemented
  void sleep() {
    print('sleep'); }}Copy the code

instantiation

final animal = Animal();
// Instantiation of an abstract class fails
// Error: The class 'Test' is abstract and can't be instantiated.
Copy the code
  • Abstract classes cannot be instantiated.
  • Inheritance: Subclasses compare and implement abstract methods, and subclasses may not override methods already implemented in abstract classes.
  • Interface: All methods declared in an abstract class must be implemented

Instantiation of abstract classes

Dart maps and Lists are abstract classes that can be used to create instance objects

final list = List(a);final dict = Map<String.dynamic> ();Copy the code

Let’s look at the Map source code:

Map is an abstract class, but notice at this point that in this abstract class, Map defines a factory constructor. This is the key to making the abstract class instantiable, because the factory method can return an instance object, but that object doesn’t have to be of the same type as the current class!

There is no concrete implementation of Map’s factory method at this point, just a keyword external in front of the factory constructor. The external keyword separates the declaration of a method from the implementation. That is, the implementation of a method can be done for us by the external. How can the external be associated with the declared method? This is where the @patch annotation is needed to bind the external method implementation to the declared method

  • externalMethod declaration and implementation can be separated
  • @patchTo associate with a classexternalThe implementation of a modified method

You can find the Map implementation source code according to the following path

// flutter/bin/cache/dart-sdk/lib/_internal/vm/lib/map_patch.dart

@patch
factory Map() = >new LinkedHashMap<K, V>();
Copy the code

As you can see, LinkedHashMap is used to implement the Map.

Let’s take a look at the source code of the LinkedHashMap implementation as follows:

// flutter/bin/cache/dart-sdk/lib/collection/linked_hash_map.dart

external factory LinkedHashMap(
    {bool Function(K, K)? equals,
    int Function(K)? hashCode,
    bool Function(dynamic)? isValidKey});
Copy the code

Here again we see that LinkedHashMap is just a declaration, finding an implementation

// flutter/bin/cache/dart-sdk/lib/_internal/vm/lib/collection_patch.dart

@patch
class LinkedHashMap<K.V> {
  @patch
  factory LinkedHashMap(
      {boolequals(K key1, K key2)? .inthashCode(K key)? .boolisValidKey(potentialKey)? {})if (isValidKey == null) {
      if (hashCode == null) {
        if (equals == null) {
          return new _InternalLinkedHashMap<K, V>();
        }
        hashCode = _defaultHashCode;
      } else {
        if (identical(identityHashCode, hashCode) &&
            identical(identical, equals)) {
          return new _CompactLinkedIdentityHashMap<K, V>();
        }
        equals ??= _defaultEquals;
      }
    } else{ hashCode ?? = _defaultHashCode; equals ?? = _defaultEquals; }return new_CompactLinkedCustomHashMap<K, V>(equals, hashCode, isValidKey); }... }Copy the code

As you can see, the LinkedHashMap () method returns an instance of the factory structure type is _InternalLinkedHashMap or _CompactLinkedCustomHashMap, here we’ll have a look at these two classes of realize the source code

// flutter/bin/cache/dart-sdk/lib/_internal/vm/lib/compact_hash.dart

@pragma("vm:entry-point")
class _InternalLinkedHashMap<K.V> extends _HashVMBase
    with
        MapMixin<K.V>,
        _LinkedHashMapMixin<K.V>,
        _HashBase._OperatorEqualsAndHashCode
    implements LinkedHashMap<K.V> {
  _InternalLinkedHashMap() {
    _index = new Uint32List(_HashBase._INITIAL_INDEX_SIZE);
    _hashMask = _HashBase._indexSizeToHashMask(_HashBase._INITIAL_INDEX_SIZE);
    _data = new List.filled(_HashBase._INITIAL_INDEX_SIZE, null);
    _usedData = 0;
    _deletedKeys = 0; }}...class _CompactLinkedIdentityHashMap<K.V> extends _HashFieldBase
    with
        MapMixin<K.V>,
        _LinkedHashMapMixin<K.V>,
        _HashBase._IdenticalAndIdentityHashCode
    implements LinkedHashMap<K.V> {
  _CompactLinkedIdentityHashMap() : super(_HashBase._INITIAL_INDEX_SIZE);
}

class _CompactLinkedCustomHashMap<K.V> extends _HashFieldBase
    with MapMixin<K.V>, _LinkedHashMapMixin<K.V>, _HashBase
    implements LinkedHashMap<K.V> {
  final _equality;
  final _hasher;
  final _validKey;

  // TODO(koda): Ask gbracha why I cannot have fields _equals/_hashCode.
  int _hashCode(e) => _hasher(e);
  bool _equals(e1, e2) => _equality(e1, e2);

  bool containsKey(Object? o) => _validKey(o) ? super.containsKey(o) : false;
  V? operator[] (Object? o) => _validKey(o) ? super[o] : null;
  V? remove(Object? o) => _validKey(o) ? super.remove(o) : null;

  _CompactLinkedCustomHashMap(this._equality, this._hasher, validKey) : _validKey = (validKey ! =null)? validKey :new _TypeTest<K>().test,
        super(_HashBase._INITIAL_INDEX_SIZE);
}
Copy the code

They are normal classes with no factory constructors, that is, external Factory Map() in Map; Eventually returned to the final instance types for _InternalLinkedHashMap or _CompactLinkedCustomHashMap

We can do a simple test

final map = Map(a);print(map.runtimeType);

// Print the result
// _InternalLinkedHashMap<dynamic, dynamic>
Copy the code

Let’s try instantiating an abstract class

abstract class Animal {
  void eat();

  void sleep() {
    print("Sleep");
  }

  factory Animal() {
    returnCat(); }}class Cat implements Animal {
  void eat() {
    print("Eat");
  }

  void sleep() {
    print('sleep'); }}Copy the code
final animal = Animal();
print(animal.runtimeType); 

// Print the result: Cat
Copy the code

Now, some of you might want to ask, well, this is interface, can we use inheritance? A factory constructor defined in an abstract class cannot be defined in a subclass other than the factory constructor

To sum up:

Abstract classes cannot be instantiated directly, but they can be instantiated indirectly by implementing the factory constructor!

Third, add

So why not just make it happen when you announce it? 🤔 The advantages of this are:

  • Declarations that reuse the same SET of apis
  • Different implementations can be made for different platforms

Different implementations for different platforms can be seen in the source code provided below

// flutter/bin/cache/dart-sdk/lib/io/file_system_entity.dart
abstract class _FileSystemWatcher {
  external static Stream<FileSystemEvent> _watch(
      String path, int events, bool recursive);
  external static bool get isSupported;
}
Copy the code
// flutter/bin/cache/dart-sdk/lib/_internal/vm/bin/file_patch.dart

@patch
static Stream<FileSystemEvent> _watch(
    String path, int events, bool recursive) {
  if (Platform.isLinux) {
    return new _InotifyFileSystemWatcher(path, events, recursive)._stream;
  }
  if (Platform.isWindows) {
    return new _Win32FileSystemWatcher(path, events, recursive)._stream;
  }
  if (Platform.isMacOS) {
    return new _FSEventStreamFileSystemWatcher(path, events, recursive)
        ._stream;
  }
  throw new FileSystemException(
      "File system watching is not supported on this platform");
}
Copy the code