Вот и подошли к одной из интересных тем - ООП. Принципы здесь как и во многих других языках программирования.
// Определяем класс class CompetitorWait { // Определяем поля внутри класса задавая им тип int number; int timestamp; // Конструктор должен совпадать по наименованию с именем класса и будет выполнен при создании объекта, при этом мы можем указать входные переменные как обязательные, так и необязательные в квадратных скобках. В теле конструктор указываем что с ними делать CompetitorWait ( int number , [ int timestamp = 0 ] ) { this.number = number; this.timestamp = timestamp; } // Мы можем переопределить дефолтные функции объекта, например что выводить при печати @override String toString ( ) { return 'Number = [$number] Start = [$timestamp]'; } } // Программа void main ( ) { var sportsmenWait = CompetitorWait ( 15 , 1600000000000 ); print ( sportsmenWait ); // Number = [15] Start = [1600000000000] }
Аннотация @override применяется всегда когда мы хотим переопределить методы базового класса.
Наследование задается через ключевое слово extends. При этом важно: конструкторы не наследуются.
// Создаем класс и наследуем от предыдущего class CompetitorFixed extends CompetitorWait { bool isSync = false; // Собственный конструктор, который обращается к конструктору родителя через super, но безусловно можно этого не делать CompetitorFixed ( { number , timestamp } ) : super ( number , timestamp ); @override String toString ( ) { return 'Number = [$number] Fixed = [$timestamp] Sync = [${isSync ? 'OK' : '?'}]'; } } // Программа void main ( ) { var sportsmenFix = CompetitorFixed ( number : 17 , timestamp : 1600000000001 ); print ( sportsmenFix ); // Number = [17] Fixed = [1600000000001] Sync = [?] }
Усложним процесс создания - добавим именованные конструкторы, т.е. мы можем указать какой именно конструктор использовать при создании объекта. Расширим предыдущий класс:
// Подключим математический пакет, он будет необходим для функции с рандомом import 'dart:math'; class CompetitorFixed extends CompetitorWait { bool isSync = false; CompetitorFixed ( { number , timestamp } ) : super ( number , timestamp ); // Именованный конструктор CompetitorFixed.timefix ( number ) : super ( number , new DateTime.now().millisecondsSinceEpoch ); // Для примера обращения через функцию и через собственный основной конструктор (this), который всё также выводит на родительский CompetitorFixed.random ( ) : this ( _randomNumber ( ) , new DateTime.now().millisecondsSinceEpoch ) { this.isSync = true; } // Для примера вычислений с инициализаторами CompetitorFixed.randomMicroseconds ( microseconds ) : this.isSync = microseconds % 10 > 4 , super ( _randomNumber ( ) ) { print ( microseconds ); this.timestamp = microseconds ~/ 1000; } // Функция static int _randomNumber ( ) { return Random ( ).nextInt ( 80 ) + 20; } @override String toString ( ) { return 'Number = [$number] Fixed = [$timestamp] Sync = [${isSync ? 'OK' : '?'}]'; } } // Программа void main ( ) { var sportsmenFixTime = CompetitorFixed.timefix ( 18 ); print ( sportsmenFixTime ); // Number = [18] Fixed = [1600426352438] Sync = [?] sportsmenFixTime.isSync = true; print ( sportsmenFixTime ); // Number = [18] Fixed = [1600426352438] Sync = [OK] var sportsmenFixRandom = CompetitorFixed.random ( ); print ( sportsmenFixRandom ); // Number = [64] Fixed = [1600426352438] Sync = [OK] var sportsmenFixRandomMicroseconds = CompetitorFixed.randomMicroseconds ( new DateTime.now().microsecondsSinceEpoch ); // 1600426352438000 print ( sportsmenFixRandomMicroseconds ); // Number = [64] Fixed = [1600426352438] Sync = [?] }
Также бывают константные конструкторы
// Константные конструкторы class ImmutablePoint { final num x, y; const ImmutablePoint(this.x, this.y); } // Программа void main ( ) { var a = const ImmutablePoint(1, 1); var b = const ImmutablePoint(1, 1); var c = ImmutablePoint(1, 1); var d = ImmutablePoint(1, 1); print ( a == b ); // true print ( b == c ); // false print ( c == d ); // false }
Особенность таких конструкторов - они не содержат тело.
Можно было заметить что полями класса выступают константы определённые через final. Такие переменные должны быть заданы изначально, либо до выполнения конструктора, т.е. либо передаваться в параметрах, либо задаваться в инициализаторе конструктора.
class ClassConstants { final num x; final num y; final num z = 1; ClassConstants (this.x) : this.y = 2 { //this.z = 3; // так уже нельзя }; }
Фабричные конструкторы позволяют не создавать каждый раз новый объект, а использовать уже существующий
// Фабричные конструкторы class Logger { final String name; static final Map<String, Logger> _cache = <String, Logger>{}; factory Logger(String name) { return _cache.putIfAbsent( name, () => Logger._internal(name)); } Logger._internal(this.name) { print ( 'Call constructor: ${this.name}' ); } } // Программа void main ( ) { Logger logger1 = Logger ( 'test' ); // Call constructor: test Logger logger2 = Logger ( 'testing' ); // Call constructor: testing Logger logger3 = Logger ( 'test' ); Logger logger4 = Logger ( 'test!' ); // Call constructor: test! }