Dart является объектно-ориентированным языком, и каждое значение, которым мы манипулируем в программе на Dart, является объектом.
Объект представляет экземпляр некоторого класса, а класс является шаблоном или описанием объекта. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке - наличие двух рук, двух ног, головы, туловища и т.д. Есть некоторый шаблон - этот шаблон можно назвать классом. Реально же существующий человек (фактически экземпляр данного класса) является объектом этого класса.
Класс определяется с помощью ключевого слова сlass:
class Person{ }
Здесь определен класс Person. После названия класса идут фигурные скобки, между которыми помещается тело класса - то есть его поля и методы. Несмотря на то, что класс фактически пуст, но это фактически уже новый тип. И мы можем определять переменные этого типа.
class Person{ } // пустой класс Person void main() { Person tom; // определяем переменную класса Person }
В данном случае переменная tom представляет класс Person.
Переменная tom еще не представляет никакой объект, и использовать ее никак нельзя. Нам надо создать объект класса Person. Для создания объектов класса применяются специальные функции, которые называются по имени класса - конструкторы. Если в классе не определено ни одного конструктора (как в случае класса Person), то для этого класса автоматически создается конструктор без параметров. Применим конструктор по умолчанию для создания объекта Person:
class Person{ } // пустой класс Person void main() { Person tom = Person(); // создаем объект класса Person print(tom); }
Для создания объекта Person используется выражение Person()
.
Конструктор по умолчанию не принимает никаких параметров. В итоге после выполнения данного выражения в памяти
будет выделен участок, где будут храниться все данные объекта Person. А переменная tom
получит ссылку на созданный объект. И мы сможем использовать ее, например, передать
в функцию print и вывести на консоль. Хотя консоль малоинформативное сообщение вроде "Instance of 'Person'", но без вызова конструктора мы с переменной tom даже этого не смогли бы сделать.
Любой объект может обладать двумя основными характеристиками: состояние - некоторые данные, которые хранит объект, и поведение - действия, которые может совершать объект.
Для хранения состояния объекта в классе применяются поля или переменные класса. Для определения поведения объекта в классе применяются методы. Например, класс Person, который представляет человека, мог бы иметь следующее определение:
class Person{ String name = ""; // имя int age = 0; // возраст void display(){ print("Name: $name \tAge: $age"); } }
В классе Person определены два поля: name представляет имя человека, а age - его возраст. И также определен метод display, который ничего не возвращает и просто выводит эти данные на консоль.
После создания объекта мы можем обратиться к переменным и методам объекта Person через имя переменной. Для этого используется оператор точка (.) -
то есть через точку указываем название поля или метода: tom.name
.
имя_объекта.поле имя_объекта.метод()
Например, используем переменные и метод класса Person:
class Person{ String name = ""; // имя int age = 0; // возраст void display(){ print("Name: $name \tAge: $age"); } } void main() { Person tom = Person(); // создаем объект класса Person print(tom.age); // обращаемся к полю age tom.display(); // обращаемся к методу display // изменяем имя и возраст tom.name = "Tom"; tom.age = 38; tom.display(); }
Таким образом, обратившись к полям объекта, мы можем получить или изменить их значения:
print(tom.age); tom.name = "Tom"; tom.age = 38;
Обратившись к методам объекта, мы можем вызвать действия, которые прописаны в этом методе:
tom.display();
Консольный вывод программы:
Unknown Name: Age: 0 Name: Tom Age: 38
Стоит учитывать, что поскольку переменные name и age представляют типы String и int, которые не допускают значение null
, то нам необходимо предоставить этим переменным начальные
значения. Либо мы могли бы использовать nullable-типы, тогда предоставление начальных значений было бы необязательно:
class Person{ String? name; // равно null int? age; // равно null void display(){ print("Name: $name \tAge: $age"); } } void main() { Person tom = Person(); tom.display(); // Name: null Age: null tom.name = "Tom"; tom.age = 38; tom.display(); // Name: Tom Age: 38 }
В данном случае поля name и age по умолчанию равны null
. И в данном случае мы получим следующий консольный вывод:
Name: null Age: null Name: Tom Age: 38
В то же время к null-переменным следует относится осторожно, поскольку если мы вдруг задействуем такие переменные до установки им конкретного значения, то мы столкнемся с ошибкой:
Person tom = Person(); tom.age = tom.age + 1; // ! Ошибка
Поэтому более предпочтительным вариантом является все таки установка некоторых начальных значений.
Каскадная нотация - операция .. позволяет выполнить последовательность операций над одним объектом:
class Person{ String name = ""; int age = 0; void display(){ print("Name: $name \tAge: $age"); } } void main (){ Person tom = Person() ..name = "Tom" ..age = 38 ..display(); // Name: Tom Age: 38 }
В данном случае код в функции main будет аналогичен следующему:
void main (){ Person tom = Person(); tom.name = "Tom" tom.age = 36 tom.display(); // Name: Tom Age: 36 }
Переменный и константы классов в языке Dart фактически представляют ссылки на объекты классов в памяти. Это означает, что если одной переменной класса присваивается значение другой переменной, то переменная в реальности получает ссылку на тот же объект в памяти. И таким образом, две переменных будут ссылаться на один и тот же объект в памяти. Посмотрим на примере:
class Person{ String name =""; } void main() { Person tom = Person(); tom.name = "Tom"; Person bob = tom; // bob указывает на тот же объект, что и tom bob.name = "Bob"; // меняем у boba имя print(tom.name); // Bob - у toma тоже поменялось имя }
После присвоения переменной bob значения переменной tom обе эти переменных будут ссылаться на один и тот же объект в памяти:
Person bob = tom;
Соответственно если мы у объекта bob изменим значение переменной name:
bob.name = "Bob";
То для объекта tom также изменится значение name, потому что tom и bob указывают на один и тот же объект:
print(tom.name); // Bob
Поэтому если необходимо копировать данные из одного объекта в другой и чтобы оба объекта существовали и изменялись независимо друг от друга, то надо использовать какой-то другой механизм, например, создавать два независимых объекта и банально копировать значения одного объекта в другой.