1/Путь к первому мобильному приложению. Часть 1.8 - Изучаем язык Dart: ООП. Конструкторы и элементы экземпляра

Вот и подошли к одной из интересных тем - ООП. Принципы здесь как и во многих других языках программирования.

// Определяем класс
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!
}