Класс class

Что такое класс и объект

Класс — это категория, способ классификации. Мы абстрагируем окружающие предметы и объединяем их в единый тип. Например, все люди на планете объединяем в класс «Человек». Конкретный человек, как Боб или Том, является объектом.

Класс выступает как «чертеж» или «шаблон», который определяет структуру и поведение всех связанных с ним объектов.

Возьмем тот же пример с классом Человек: этот шаблон описывает, что есть у человека и что он может делать. У него есть имя, руки, ноги и другие органы, он может говорить и думать. Данные как имя или возраст — это свойства класса, а действия как разговор или размышление — его поведение. Иными словами, класс выделяет общие признаки из реальных объектов. По этому шаблону можно создавать отдельные объекты, например Джек или Джим.

Свойства класса (имя, возраст) и его поведение (разговор, мышление) представлены переменными и функциями соответственно.

В нем прописаны свойства (переменные) и методы (функции), которые должен иметь каждый объект.

Класс можно сравнить с формой для выпечки хлеба: сама форма не является готовым изделием. Из одной формы получается много одинаковых булок, так же из одного класса создается множество объектов.

В начале истории программирования понятия класса не существовало. Яркий пример — язык C, в котором используются только функции, переменные и операторы, такой подход называется процедурным программированием. По мере усложнения программ выявлялись недостатки этой модели: много дублирующего кода, хаотичная структура, отсутствие четкой иерархии. Позже появился объектно-ориентированный подход: логика программы абстрагируется в отдельные объекты, как в реальном мире. Сравним с фирмой: сначала руководитель сам выполняет все задачи, после найма сотрудников бухгалтерию ведет финансовый отдел, подбор персонала занимается кадровая служба. Разделение обязанностей делает работу упорядоченной.

Объект — это экземпляр класса.

При использовании ключевого слова new или конструктора класс генерирует новый объект.

Объект — это реальная сущность во время выполнения программы, способная хранить данные и вызывать методы.

Связь между классами и объектами

  • Класс — это архитектурный чертеж, объект — дом, построенный по этому чертежу.
  • Из одного класса можно создать множество объектов, каждый хранит собственные независимые данные.
  • Все объекты, созданные из одного класса, разделяют поведение (методы), прописанные в классе.

Теперь перейдем к языку Dart

Мы разберем следующие темы

Dart — полностью объектно-ориентированный язык, любое значение является экземпляром класса. За исключением Null все классы наследуют базовый класс Object.

Наследование в Dart реализовано через комбинацию классов и миксинов (Mixin)

Одиночное наследование: каждый класс имеет только один родительский класс, но миксины позволяют переиспользовать логику из нескольких разных классов

Расширяющие методы: добавляют новые функции в класс без изменения исходного кода или создания подкласса

Модификаторы классов: регулируют, могут ли другие библиотеки наследовать или реализовывать данный класс

Начнем изучать классы в Dart

Члены класса

Члены класса объединяют методы и переменные — это все элементы, из которых состоит класс.

Оператор . используется для доступа к членам экземпляра; оператор безопасного доступа к нулю ?. предотвращает исключения нулевого указателя

Ниже приведен полный пример, демонстрирующий объявление класса, создание объекта из класса и вызов его членов по имени объекта

// Объявить класс — это как создать шаблон-форму
class Point {
  double x, y;
  Point(this.x, this.y);

  // Метод экземпляра: вычислить квадрат расстояния между двумя точками
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  // Создать объект p из класса Point для выполнения операций
  var p = Point(2, 2);

  // Прочитать переменную экземпляра (неявный геттер)
  print(p.y); //2.0

  // Вызвать метод экземпляра
  double dist = p.distanceTo(Point(4, 4));
  print(dist); // 8.0

  // Безопасный доступ через ?.
  Point? nullablePoint;
  var a = nullablePoint?.y; // Если объект равен null, в a запишется null без ошибки
  print(a); // null
}Code language: JavaScript (javascript)

Конструкторы (Constructors)

Стандартные конструкторы

Dart отличается от других C-подобных языков поддержкой именованных конструкторов. В большинстве языков на основе C конструктор должен совпадать по названию с классом, а в Dart можно создавать конструкторы с идентификатором, отличным от имени класса.

Синтаксис: ИмяКласса() / ИмяКласса.Идентификатор() ;

Ключевое слово new можно опускать (поддерживается с Dart 2)

class Point {
  double x, y; // Две переменные-члены для хранения свойств класса

  // Стандартный конструктор: инициализировать переменные x и y
  Point(this.x, this.y);

  // Именованный конструктор Point.fromJson: инициализация x и y из данных JSON
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;

}

void main() {
  var p1 = Point(2, 2);//Вызов стандартного конструктора, передача 2 и 2, x=2, y=2 для p1

  var p2 = Point.fromJson({'x': 1, 'y': 2}); //Передача JSON в именованный конструктор fromJson

  // new можно писать или опускать, поведение полностью одинаковое
  var p3 = new Point(3, 3); //Создание объекта через стандартный конструктор

  print("p1:(${p1.x},${p1.y}), p2:(${p2.x},${p2.y}), p3:(${p3.x},${p3.y})");
  //Вывод: p1:(2.0,2.0), p2:(1.0,2.0), p3:(3.0,3.0)
}Code language: JavaScript (javascript)

Константные конструкторы с const

Перед конструктором добавьте ключевое слово const, чтобы создать константные объекты времени компиляции; константы с одинаковыми параметрами используют один и тот же экземпляр повторно.

class ImmutablePoint {
  final double x, y;
  // Правило константного конструктора: все члены помечаются модификатором final
  const ImmutablePoint(this.x, this.y);
}

void main() {
  // Создание константных экземпляров
  var a = const ImmutablePoint(1, 1); // Ключевое слово const обязательно здесь
  var b = const ImmutablePoint(1, 1);
  print(identical(a, b)); // true, ссылаются на один объект

  var m = const ImmutablePoint(1, 1); 
  var n = const ImmutablePoint(2, 1);
  print(identical(m, n)); // false, разные значения создают отдельные объекты

  // Без const — обычный объект, разделения экземпляров нет
  var c = ImmutablePoint(1, 1);
  print(identical(a, c)); //false

  // В контексте const внутреннее const можно опустить
  const pointMap = {
    'p1': [ImmutablePoint(0, 0)],
    'p2': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)]
  };

  print(pointMap); //{p1: [Instance of 'ImmutablePoint'], p2: [Instance of 'ImmutablePoint', Instance of 'ImmutablePoint']}

  print(identical(p1, p2)); // false
  print(identical(p2, p3)); // false
  print(identical(p1, p3)); // false
}Code language: PHP (php)
Константный конструктор повторно использует экземпляр только при полном совпадении всех передаваемых параметров; хотя бы один разный параметр создаст два независимых объекта.

Получить тип объекта во время выполнения через runtimeType

Тип во время выполнения берется через объект.runtimeType; лучше использовать оператор проверки типа is, а не сравнения через runtimeType

class Point {
  double x, y;
  Point(this.x, this.y);

  // Метод экземпляра: вычислить квадрат расстояния между двумя точками
  double distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return dx * dx + dy * dy;
  }
}

void main() {
  var p = Point(2, 3);
  print("Тип p: ${p.runtimeType}"); // Point

  // Рекомендованный стабильный способ проверки
  if (p is Point) {
    print("p является экземпляром Point");
  }

  // Не рекомендуется для рабочего кода
  if (p.runtimeType == Point) {
    print("p является экземпляром Point ");
  }
}Code language: JavaScript (javascript)

Переменные экземпляра Instance Variables

  1. Неназначенные нулевые типы по умолчанию равны null; ненулевые типы обязательно нужно инициализировать
  2. Все переменные экземпляра автоматически получают неявный геттер; не-final или неинициализированные late final переменные имеют автоматический сеттер
  3. Обычные выражения инициализации переменных экземпляра не имеют доступа к this; для late переменных доступ к this при инициализации разрешен
  4. Final переменные экземпляра получают значение только один раз: при объявлении, через параметр конструктора или в списке инициализации конструктора

Неявные геттеры и сеттеры

class Point {
  double? x; // Начальное значение null, геттер + сеттер создаются автоматически
  double? y;
  double z = 0; // Инициализировано нулем
}

void main() {
  var point = Point();
  point.x = 4; // Вызов неявного сеттера
  assert(point.x == 4); // Вызов неявного геттера
  assert(point.y == null);
  print(point.z); // 0
}
Code language: JavaScript (javascript)

Ограничение доступа к this в выражениях инициализации

double globalX = 1.5;
class Point {
  // Разрешено: обращение к внешней глобальной переменной без this
  double? x = globalX;

  // Ошибка компиляции: стандартная инициализация не допускает this
  // double? y = this.x;

  // Разрешено: late переменные могут ссылаться на this при инициализации
  late double? z = this.x;

  // Разрешено: синтаксический сахар this.x в параметрах конструктора не является выражением инициализации
  Point(this.x, this.y);
  double? y;
}

void main() {
  var p = Point(null, 5);
  print(p.z); // null
}
Code language: JavaScript (javascript)

Три способа инициализации final переменных экземпляра

class ProfileMark {
  // Способ 1: прямая инициализация при объявлении
  final DateTime start = DateTime.now();
  final String name;

  // Способ 2: присваивание через параметр конструктора
  ProfileMark(this.name);

  // Способ 3: присваивание в списке инициализации конструктора; именованный конструктор unnamed по умолчанию записывает пустую строку в name
  ProfileMark.unnamed() : name = '';
}

void main() {
  var m1 = ProfileMark("user1");
  var m2 = ProfileMark.unnamed();
  print(m1.name);
  print(m2.name);
}
Code language: PHP (php)

Неявные интерфейсы Implicit interfaces

Каждый класс Dart имеет неявный интерфейс, включающий все открытые члены экземпляра класса.

  • implements: реализовать интерфейс, нужно переопределить все методы интерфейса
  • extends: наследовать родительский класс для переиспользования его реализации
  • Закрытые переменные с префиксом _xxx не экспортируются в интерфейс

1 Реализация одного интерфейса

class Person {
  final String _name;
  Person(this._name);
  // Открытый метод интерфейса
  String greet(String who) =&gt; "Hello $who, I'm $_name";
}

// Реализовать интерфейс Person: обязательно переопределить все его члены
class Impostor implements Person {
  String get _name =&gt; "";
  @override
  String greet(String who) =&gt; "Hi $who, guess who?";
}

String greetBob(Person p) =&gt; p.greet("Bob");

void main() {
  print(greetBob(Person("Kathy")));
  print(greetBob(Impostor()));
}
Code language: PHP (php)

При переопределении метода родительского интерфейса в подклассе используйте аннотацию @override, как в Java. Для реализации интерфейса применяется ключевое слово implements.

2 Реализация нескольких интерфейсов

// Абстрактный класс
abstract class Comparable {
  int compareTo(Object other);
}

// Абстрактный класс
abstract class Location {
  double get x;
  double get y;
}

// Одновременная реализация двух интерфейсов
class Point implements Comparable, Location {

  @override
  double x, y;

  Point(this.x, this.y);

  @override
  int compareTo(Object other) {
    if (other is Point) {
      return (x + y).compareTo(other.x + other.y);
    }
    return -1;
  }
}

void main() {
  var p = Point(2, 3);
  print(p.x);
  print(p.compareTo(Point(1, 1)));
}
Code language: PHP (php)

@override
double x, y;

Эта аннотация означает, что эти два свойства переопределяют абстрактные геттеры, объявленные в родительском интерфейсе Location.

Статические переменные & статические методы static

1. Статические переменные static (классовые переменные, глобально единственный экземпляр)

  • Принадлежат самому классу, а не экземплярам; отложенная инициализация: создаются только при первом обращении
  • Статические константы рекомендуется именовать в нижнем верблюжьем стиле (camelCase)
class Queue {
  static const initialCapacity = 16; // Статическая константа
  static int count = 0; // Изменяемая статическая переменная
  Queue() {
    count++;
  }
}

void main() {
  assert(Queue.initialCapacity == 16); // Инициализируется при обращении, значение по умолчанию 16
  Queue(); // Увеличить count на единицу
  Queue();
  print(Queue.count); // 2
}
Code language: JavaScript (javascript)

2. Статические методы static (классовые методы)

  • Не имеют ссылки this, поэтому не могут обращаться к членам экземпляра; доступны только статические переменные
  • Вызываются напрямую по синтаксису ИмяКласса.Метод()
  • Могут передаваться как константы времени компиляции в константные конструкторы
import 'dart:math';

class Point {
  double x, y;
  Point(this.x, this.y);

  // Статический метод: вычислить прямое расстояние между двумя точками
  static double distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var dist = Point.distanceBetween(a, b);
  print(dist); // ~2,828
}Code language: JavaScript (javascript)

Статические методы принадлежат классу, поэтому их нужно вызывать по имени класса, передавая два экземпляра Point как аргументы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *